aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Shah <davey1576@gmail.com>2018-11-15 11:26:08 +0000
committerGitHub <noreply@github.com>2018-11-15 11:26:08 +0000
commit9472b6d78f68544d430feeae6d75dbd2dc43019d (patch)
tree0ab6b20c90d4a93cd9e2d0c14bdadb296e159cdc
parentd3b2065cd7d2470a132c055f4bd88d270e1e8fe1 (diff)
parent9f9b242cf0a3b587df8f5b0eb542ca7256ca0eb9 (diff)
downloadnextpnr-9472b6d78f68544d430feeae6d75dbd2dc43019d.tar.gz
nextpnr-9472b6d78f68544d430feeae6d75dbd2dc43019d.tar.bz2
nextpnr-9472b6d78f68544d430feeae6d75dbd2dc43019d.zip
Merge pull request #103 from YosysHQ/timingapi
Timing constraints API, multiple clock domains
-rw-r--r--common/nextpnr.cc111
-rw-r--r--common/nextpnr.h116
-rw-r--r--common/place_common.cc4
-rw-r--r--common/router1.cc2
-rw-r--r--common/timing.cc544
-rw-r--r--common/timing.h2
-rw-r--r--docs/archapi.md16
-rw-r--r--docs/constraints.md37
-rw-r--r--ecp5/arch.cc78
-rw-r--r--ecp5/arch.h12
-rw-r--r--ecp5/arch_pybindings.cc4
-rw-r--r--ecp5/globals.cc7
-rw-r--r--generic/arch.cc7
-rw-r--r--generic/arch.h14
-rw-r--r--ice40/arch.cc80
-rw-r--r--ice40/arch.h12
-rw-r--r--ice40/arch_pybindings.cc4
-rw-r--r--ice40/pack.cc11
18 files changed, 881 insertions, 180 deletions
diff --git a/common/nextpnr.cc b/common/nextpnr.cc
index 903ab9e4..be3bfe14 100644
--- a/common/nextpnr.cc
+++ b/common/nextpnr.cc
@@ -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);
}
+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 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
diff --git a/common/nextpnr.h b/common/nextpnr.h
index 4434c438..a6617ae4 100644
--- a/common/nextpnr.h
+++ b/common/nextpnr.h
@@ -194,6 +194,14 @@ struct Loc
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
namespace std {
@@ -208,6 +216,15 @@ template <> struct hash<NEXTPNR_NAMESPACE_PREFIX Loc>
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
#include "archdefs.h"
@@ -266,6 +283,8 @@ struct PipMap
PlaceStrength strength = STRENGTH_NONE;
};
+struct ClockConstraint;
+
struct NetInfo : ArchNetInfo
{
IdString name;
@@ -278,6 +297,10 @@ struct NetInfo : ArchNetInfo
// wire -> uphill_pip
std::unordered_map<WireId, PipMap> wires;
+ std::unique_ptr<ClockConstraint> clkconstr;
+
+ TimingConstrObjectId tmg_id;
+
Region *region = nullptr;
};
@@ -293,6 +316,7 @@ struct PortInfo
IdString name;
NetInfo *net;
PortType type;
+ TimingConstrObjectId tmg_id;
};
struct CellInfo : ArchCellInfo
@@ -320,6 +344,7 @@ struct CellInfo : ArchCellInfo
// parent.[xyz] := 0 when (constr_parent == nullptr)
Region *region = nullptr;
+ TimingConstrObjectId tmg_id;
};
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
};
+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
{
uint64_t rngstate;
@@ -431,6 +518,11 @@ struct BaseCtx
idstring_idx_to_str = new std::vector<const std::string *>;
IdString::initialize_add(this, "", 0);
IdString::initialize_arch(this);
+
+ TimingConstraintObject wildcard;
+ wildcard.id.index = 0;
+ wildcard.type = TimingConstraintObject::ANYTHING;
+ constraintObjects.push_back(wildcard);
}
~BaseCtx()
@@ -524,6 +616,30 @@ struct BaseCtx
void refreshUiPip(PipId pip) { pipUiReload.insert(pip); }
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
diff --git a/common/place_common.cc b/common/place_common.cc
index 4cb5ae11..a13a963c 100644
--- a/common/place_common.cc
+++ b/common/place_common.cc
@@ -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);
if (driver_gb)
return 0;
- IdString clock_port;
- bool timing_driven = ctx->timing_driven && type == MetricType::COST && ctx->getPortTimingClass(driver_cell, net->driver.port, clock_port) != TMG_IGNORE;
+ int clock_count;
+ 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 worst_slack = std::numeric_limits<delay_t>::max();
Loc driver_loc = ctx->getBelLocation(driver_cell->bel);
diff --git a/common/router1.cc b/common/router1.cc
index adad37e9..958c24d4 100644
--- a/common/router1.cc
+++ b/common/router1.cc
@@ -808,7 +808,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg)
#endif
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();
return true;
diff --git a/common/timing.cc b/common/timing.cc
index c5a99f54..40e4d344 100644
--- a/common/timing.cc
+++ b/common/timing.cc
@@ -22,6 +22,7 @@
#include <algorithm>
#include <boost/range/adaptor/reversed.hpp>
#include <deque>
+#include <map>
#include <unordered_map>
#include <utility>
#include "log.h"
@@ -29,17 +30,72 @@
NEXTPNR_NAMESPACE_BEGIN
+namespace {
+struct ClockEvent
+{
+ IdString clock;
+ ClockEdge edge;
+
+ bool operator==(const ClockEvent &other) const { return clock == other.clock && edge == other.edge; }
+};
+
+struct ClockPair
+{
+ ClockEvent start, end;
+
+ bool operator==(const ClockPair &other) const { return start == other.start && end == other.end; }
+};
+} // namespace
+
+NEXTPNR_NAMESPACE_END
+namespace std {
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockEvent &obj) const noexcept
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(obj.clock));
+ boost::hash_combine(seed, hash<int>()(int(obj.edge)));
+ return seed;
+ }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockPair>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockPair &obj) const noexcept
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start));
+ boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start));
+ return seed;
+ }
+};
+
+} // namespace std
+NEXTPNR_NAMESPACE_BEGIN
+
typedef std::vector<const PortRef *> PortRefVector;
typedef std::map<int, unsigned> DelayFrequency;
+struct CriticalPath
+{
+ PortRefVector ports;
+ delay_t path_delay;
+ delay_t path_period;
+};
+
+typedef std::unordered_map<ClockPair, CriticalPath> CriticalPathMap;
+
struct Timing
{
Context *ctx;
bool net_delays;
bool update;
delay_t min_slack;
- PortRefVector *crit_path;
+ CriticalPathMap *crit_path;
DelayFrequency *slack_histogram;
+ IdString async_clock;
struct TimingData
{
@@ -49,23 +105,24 @@ struct Timing
unsigned max_path_length = 0;
delay_t min_remaining_budget;
bool false_startpoint = false;
+ std::unordered_map<ClockEvent, delay_t> arrival_time;
};
- Timing(Context *ctx, bool net_delays, bool update, PortRefVector *crit_path = nullptr,
+ Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr,
DelayFrequency *slack_histogram = nullptr)
: ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq),
- crit_path(crit_path), slack_histogram(slack_histogram)
+ crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$"))
{
}
delay_t walk_paths()
{
- const auto clk_period = delay_t(1.0e12 / ctx->target_freq);
+ const auto clk_period = ctx->getDelayFromNS(1.0e9 / ctx->target_freq).maxDelay();
// First, compute the topographical order of nets to walk through the circuit, assuming it is a _acyclic_ graph
// TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops
std::vector<NetInfo *> topographical_order;
- std::unordered_map<const NetInfo *, TimingData> net_data;
+ std::unordered_map<const NetInfo *, std::unordered_map<ClockEvent, TimingData>> net_data;
// In lieu of deleting edges from the graph, simply count the number of fanins to each output port
std::unordered_map<const PortInfo *, unsigned> port_fanin;
@@ -84,22 +141,34 @@ struct Timing
}
for (auto o : output_ports) {
- IdString clockPort;
- TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clockPort);
+ int clocks = 0;
+ TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clocks);
// If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing
// start-point
if (portClass == TMG_REGISTER_OUTPUT) {
- DelayInfo clkToQ;
- ctx->getCellDelay(cell.second.get(), clockPort, o->name, clkToQ);
topographical_order.emplace_back(o->net);
- net_data.emplace(o->net, TimingData{clkToQ.maxDelay()});
+ for (int i = 0; i < clocks; i++) {
+ TimingClockingInfo clkInfo = ctx->getPortClockingInfo(cell.second.get(), o->name, i);
+ const NetInfo *clknet = get_net_or_empty(cell.second.get(), clkInfo.clock_port);
+ IdString clksig = clknet ? clknet->name : async_clock;
+ net_data[o->net][ClockEvent{clksig, clknet ? clkInfo.edge : RISING_EDGE}] =
+ TimingData{clkInfo.clockToQ.maxDelay()};
+ }
+
} else {
if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) {
topographical_order.emplace_back(o->net);
TimingData td;
td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE);
- net_data.emplace(o->net, std::move(td));
+ td.max_arrival = 0;
+ net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td;
}
+
+ // Don't analyse paths from a clock input to other pins - they will be considered by the
+ // special-case handling register input/output class ports
+ if (portClass == TMG_CLOCK_INPUT)
+ continue;
+
// Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and
// the current output port, increment fanin counter
for (auto i : input_ports) {
@@ -120,14 +189,15 @@ struct Timing
queue.pop_front();
for (auto &usr : net->users) {
- IdString clockPort;
- TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort);
+ int user_clocks;
+ TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, user_clocks);
if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT)
continue;
for (auto &port : usr.cell->ports) {
if (port.second.type != PORT_OUT || !port.second.net)
continue;
- TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, clockPort);
+ int port_clocks;
+ TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, port_clocks);
// Skip if this is a clocked output (but allow non-clocked ones)
if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE ||
@@ -174,137 +244,213 @@ struct Timing
// Go forwards topographically to find the maximum arrival time and max path length for each net
for (auto net : topographical_order) {
- auto &nd = net_data.at(net);
- const auto net_arrival = nd.max_arrival;
- const auto net_length_plus_one = nd.max_path_length + 1;
- nd.min_remaining_budget = clk_period;
- for (auto &usr : net->users) {
- IdString clockPort;
- TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort);
- if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) {
- } else {
+ if (!net_data.count(net))
+ continue;
+ auto &nd_map = net_data.at(net);
+ for (auto &startdomain : nd_map) {
+ ClockEvent start_clk = startdomain.first;
+ auto &nd = startdomain.second;
+ if (nd.false_startpoint)
+ continue;
+ const auto net_arrival = nd.max_arrival;
+ const auto net_length_plus_one = nd.max_path_length + 1;
+ nd.min_remaining_budget = clk_period;
+ for (auto &usr : net->users) {
+ int port_clocks;
+ TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
- auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
auto usr_arrival = net_arrival + net_delay;
- // Iterate over all output ports on the same cell as the sink
- for (auto port : usr.cell->ports) {
- if (port.second.type != PORT_OUT || !port.second.net)
- continue;
- DelayInfo comb_delay;
- // Look up delay through this path
- bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
- if (!is_path)
- continue;
- auto &data = net_data[port.second.net];
- auto &arrival = data.max_arrival;
- arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay());
- if (!budget_override) { // Do not increment path length if budget overriden since it doesn't
- // require a share of the slack
- auto &path_length = data.max_path_length;
- path_length = std::max(path_length, net_length_plus_one);
+
+ if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE ||
+ portClass == TMG_CLOCK_INPUT) {
+ // Skip
+ } else {
+ auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
+ // Iterate over all output ports on the same cell as the sink
+ for (auto port : usr.cell->ports) {
+ if (port.second.type != PORT_OUT || !port.second.net)
+ continue;
+ DelayInfo comb_delay;
+ // Look up delay through this path
+ bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
+ if (!is_path)
+ continue;
+ auto &data = net_data[port.second.net][start_clk];
+ auto &arrival = data.max_arrival;
+ arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay());
+ if (!budget_override) { // Do not increment path length if budget overriden since it doesn't
+ // require a share of the slack
+ auto &path_length = data.max_path_length;
+ path_length = std::max(path_length, net_length_plus_one);
+ }
}
}
}
}
}
- const NetInfo *crit_net = nullptr;
+ std::unordered_map<ClockPair, std::pair<delay_t, NetInfo *>> crit_nets;
// Now go backwards topographically to determine the minimum path slack, and to distribute all path slack evenly
// between all nets on the path
for (auto net : boost::adaptors::reverse(topographical_order)) {
- auto &nd = net_data.at(net);
- // Ignore false startpoints
- if (nd.false_startpoint) continue;
- const delay_t net_length_plus_one = nd.max_path_length + 1;
- auto &net_min_remaining_budget = nd.min_remaining_budget;
- for (auto &usr : net->users) {
- auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
- auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
- IdString associatedClock;
- TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, associatedClock);
- if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) {
- const auto net_arrival = nd.max_arrival;
- auto path_budget = clk_period - (net_arrival + net_delay);
- if (update) {
- auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
- usr.budget = std::min(usr.budget, net_delay + budget_share);
- net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share);
- }
+ if (!net_data.count(net))
+ continue;
+ auto &nd_map = net_data.at(net);
+ for (auto &startdomain : nd_map) {
+ auto &nd = startdomain.second;
+ // Ignore false startpoints
+ if (nd.false_startpoint)
+ continue;
+ const delay_t net_length_plus_one = nd.max_path_length + 1;
+ auto &net_min_remaining_budget = nd.min_remaining_budget;
+ for (auto &usr : net->users) {
+ auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
+ auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
+ int port_clocks;
+ TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
+ if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) {
+ auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) {
+ const auto net_arrival = nd.max_arrival;
+ const auto endpoint_arrival = net_arrival + net_delay + setup;
+ delay_t period;
+ // Set default period
+ if (edge == startdomain.first.edge) {
+ period = clk_period;
+ } else {
+ period = clk_period / 2;
+ }
+ if (clksig != async_clock) {
+ if (ctx->nets.at(clksig)->clkconstr) {
+ if (edge == startdomain.first.edge) {
+ // same edge
+ period = ctx->nets.at(clksig)->clkconstr->period.minDelay();
+ } else if (edge == RISING_EDGE) {
+ // falling -> rising
+ period = ctx->nets.at(clksig)->clkconstr->low.minDelay();
+ } else if (edge == FALLING_EDGE) {
+ // rising -> falling
+ period = ctx->nets.at(clksig)->clkconstr->high.minDelay();
+ }
+ }
+ }
+ auto path_budget = period - endpoint_arrival;
+
+ if (update) {
+ auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
+ usr.budget = std::min(usr.budget, net_delay + budget_share);
+ net_min_remaining_budget =
+ std::min(net_min_remaining_budget, path_budget - budget_share);
+ }
+
+ if (path_budget < min_slack)
+ min_slack = path_budget;
+
+ if (slack_histogram) {
+ int slack_ps = ctx->getDelayNS(path_budget) * 1000;
+ (*slack_histogram)[slack_ps]++;
+ }
+ ClockEvent dest_ev{clksig, edge};
+ ClockPair clockPair{startdomain.first, dest_ev};
+ nd.arrival_time[dest_ev] = std::max(nd.arrival_time[dest_ev], endpoint_arrival);
+
+ if (crit_path) {
+ if (!crit_nets.count(clockPair) || crit_nets.at(clockPair).first < endpoint_arrival) {
+ crit_nets[clockPair] = std::make_pair(endpoint_arrival, net);
+ (*crit_path)[clockPair].path_delay = endpoint_arrival;
+ (*crit_path)[clockPair].path_period = period;
+ (*crit_path)[clockPair].ports.clear();
+ (*crit_path)[clockPair].ports.push_back(&usr);
+ }
+ }
+ };
+ if (portClass == TMG_REGISTER_INPUT) {
+ for (int i = 0; i < port_clocks; i++) {
+ TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, i);
+ const NetInfo *clknet = get_net_or_empty(usr.cell, clkInfo.clock_port);
+ IdString clksig = clknet ? clknet->name : async_clock;
+ process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE, clkInfo.setup.maxDelay());
+ }
+ } else {
+ process_endpoint(async_clock, RISING_EDGE, 0);
+ }
- if (path_budget < min_slack) {
- min_slack = path_budget;
- if (crit_path) {
- crit_path->clear();
- crit_path->push_back(&usr);
- crit_net = net;
+ } else if (update) {
+
+ // Iterate over all output ports on the same cell as the sink
+ for (const auto &port : usr.cell->ports) {
+ if (port.second.type != PORT_OUT || !port.second.net)
+ continue;
+ DelayInfo comb_delay;
+ bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
+ if (!is_path)
+ continue;
+ if (net_data.count(port.second.net) &&
+ net_data.at(port.second.net).count(startdomain.first)) {
+ auto path_budget =
+ net_data.at(port.second.net).at(startdomain.first).min_remaining_budget;
+ auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
+ usr.budget = std::min(usr.budget, net_delay + budget_share);
+ net_min_remaining_budget =
+ std::min(net_min_remaining_budget, path_budget - budget_share);
+ }
}
}
- if (slack_histogram) {
- int slack_ps = ctx->getDelayNS(path_budget) * 1000;
- (*slack_histogram)[slack_ps]++;
- }
- } else if (update) {
- // Iterate over all output ports on the same cell as the sink
- for (const auto &port : usr.cell->ports) {
- if (port.second.type != PORT_OUT || !port.second.net)
- continue;
- DelayInfo comb_delay;
- bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
- if (!is_path)
- continue;
- auto path_budget = net_data.at(port.second.net).min_remaining_budget;
- auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
- usr.budget = std::min(usr.budget, net_delay + budget_share);
- net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share);
- }
}
}
}
if (crit_path) {
// Walk backwards from the most critical net
- while (crit_net) {
- const PortInfo *crit_ipin = nullptr;
- delay_t max_arrival = std::numeric_limits<delay_t>::min();
-
- // Look at all input ports on its driving cell
- for (const auto &port : crit_net->driver.cell->ports) {
- if (port.second.type != PORT_IN || !port.second.net)
- continue;
- DelayInfo comb_delay;
- bool is_path =
- ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay);
- if (!is_path)
- continue;
- // If input port is influenced by a clock, skip
- IdString portClock;
- TimingPortClass portClass = ctx->getPortTimingClass(crit_net->driver.cell, port.first, portClock);
- if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT ||
- portClass == TMG_IGNORE)
- continue;
+ for (auto crit_pair : crit_nets) {
+ NetInfo *crit_net = crit_pair.second.second;
+ auto &cp_ports = (*crit_path)[crit_pair.first].ports;
+ while (crit_net) {
+ const PortInfo *crit_ipin = nullptr;
+ delay_t max_arrival = std::numeric_limits<delay_t>::min();
+
+ // Look at all input ports on its driving cell
+ for (const auto &port : crit_net->driver.cell->ports) {
+ if (port.second.type != PORT_IN || !port.second.net)
+ continue;
+ DelayInfo comb_delay;
+ bool is_path =
+ ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay);
+ if (!is_path)
+ continue;
+ // If input port is influenced by a clock, skip
+ int port_clocks;
+ TimingPortClass portClass =
+ ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
+ if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT ||
+ portClass == TMG_ENDPOINT || portClass == TMG_IGNORE)
+ continue;
- // And find the fanin net with the latest arrival time
- const auto net_arrival = net_data.at(port.second.net).max_arrival;
- if (net_arrival > max_arrival) {
- max_arrival = net_arrival;
- crit_ipin = &port.second;
+ // And find the fanin net with the latest arrival time
+ if (net_data.count(port.second.net) &&
+ net_data.at(port.second.net).count(crit_pair.first.start)) {
+ const auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival;
+ if (net_arrival > max_arrival) {
+ max_arrival = net_arrival;
+ crit_ipin = &port.second;
+ }
+ }
}
- }
- if (!crit_ipin)
- break;
-
- // Now convert PortInfo* into a PortRef*
- for (auto &usr : crit_ipin->net->users) {
- if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) {
- crit_path->push_back(&usr);
+ if (!crit_ipin)
break;
+
+ // Now convert PortInfo* into a PortRef*
+ for (auto &usr : crit_ipin->net->users) {
+ if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) {
+ cp_ports.push_back(&usr);
+ break;
+ }
}
+ crit_net = crit_ipin->net;
}
- crit_net = crit_ipin->net;
+ std::reverse(cp_ports.begin(), cp_ports.end());
}
- std::reverse(crit_path->begin(), crit_path->end());
}
return min_slack;
}
@@ -365,30 +511,106 @@ void assign_budget(Context *ctx, bool quiet)
log_info("Checksum: 0x%08x\n", ctx->checksum());
}
-void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
+void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path)
{
- PortRefVector crit_path;
+ auto format_event = [ctx](const ClockEvent &e, int field_width = 0) {
+ std::string value;
+ if (e.clock == ctx->id("$async$"))
+ value = std::string("<async>");
+ else
+ value = (e.edge == FALLING_EDGE ? std::string("negedge ") : std::string("posedge ")) + e.clock.str(ctx);
+ if (int(value.length()) < field_width)
+ value.insert(value.length(), field_width - int(value.length()), ' ');
+ return value;
+ };
+
+ CriticalPathMap crit_paths;
DelayFrequency slack_histogram;
- Timing timing(ctx, true /* net_delays */, false /* update */, print_path ? &crit_path : nullptr,
+ Timing timing(ctx, true /* net_delays */, false /* update */, (print_path || print_fmax) ? &crit_paths : nullptr,
print_histogram ? &slack_histogram : nullptr);
- auto min_slack = timing.walk_paths();
+ timing.walk_paths();
+ std::map<IdString, std::pair<ClockPair, CriticalPath>> clock_reports;
+ std::map<IdString, double> clock_fmax;
+ std::vector<ClockPair> xclock_paths;
+ std::set<IdString> empty_clocks; // set of clocks with no interior paths
+ if (print_path || print_fmax) {
+ for (auto path : crit_paths) {
+ const ClockEvent &a = path.first.start;
+ const ClockEvent &b = path.first.end;
+ empty_clocks.insert(a.clock);
+ empty_clocks.insert(b.clock);
+ }
+ for (auto path : crit_paths) {
+ const ClockEvent &a = path.first.start;
+ const ClockEvent &b = path.first.end;
+ if (a.clock != b.clock || a.clock == ctx->id("$async$"))
+ continue;
+ double Fmax;
+ empty_clocks.erase(a.clock);
+ if (a.edge == b.edge)
+ Fmax = 1000 / ctx->getDelayNS(path.second.path_delay);
+ else
+ Fmax = 500 / ctx->getDelayNS(path.second.path_delay);
+ if (!clock_fmax.count(a.clock) || Fmax < clock_fmax.at(a.clock)) {
+ clock_reports[a.clock] = path;
+ clock_fmax[a.clock] = Fmax;
+ }
+ }
+
+ for (auto &path : crit_paths) {
+ const ClockEvent &a = path.first.start;
+ const ClockEvent &b = path.first.end;
+ if (a.clock == b.clock && a.clock != ctx->id("$async$"))
+ continue;
+ xclock_paths.push_back(path.first);
+ }
+
+ if (clock_reports.empty()) {
+ log_warning("No clocks found in design");
+ }
+
+ std::sort(xclock_paths.begin(), xclock_paths.end(), [ctx](const ClockPair &a, const ClockPair &b) {
+ if (a.start.clock.str(ctx) < b.start.clock.str(ctx))
+ return true;
+ if (a.start.clock.str(ctx) > b.start.clock.str(ctx))
+ return false;
+ if (a.start.edge < b.start.edge)
+ return true;
+ if (a.start.edge > b.start.edge)
+ return false;
+ if (a.end.clock.str(ctx) < b.end.clock.str(ctx))
+ return true;
+ if (a.end.clock.str(ctx) > b.end.clock.str(ctx))
+ return false;
+ if (a.end.edge < b.end.edge)
+ return true;
+ return false;
+ });
+ }
if (print_path) {
- if (crit_path.empty()) {
- log_info("Design contains no timing paths\n");
- } else {
+ auto print_path_report = [ctx](ClockPair &clocks, PortRefVector &crit_path) {
delay_t total = 0;
- log_break();
- log_info("Critical path report:\n");
- log_info("curr total\n");
-
auto &front = crit_path.front();
auto &front_port = front->cell->ports.at(front->port);
auto &front_driver = front_port.net->driver;
- IdString last_port;
- ctx->getPortTimingClass(front_driver.cell, front_driver.port, last_port);
+ int port_clocks;
+ auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks);
+ IdString last_port = front_driver.port;
+ if (portClass == TMG_REGISTER_OUTPUT) {
+ for (int i = 0; i < port_clocks; i++) {
+ TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i);
+ const NetInfo *clknet = get_net_or_empty(front_driver.cell, clockInfo.clock_port);
+ if (clknet != nullptr && clknet->name == clocks.start.clock &&
+ clockInfo.edge == clocks.start.edge) {
+ last_port = clockInfo.clock_port;
+ }
+ }
+ }
+
+ log_info("curr total\n");
for (auto sink : crit_path) {
auto sink_cell = sink->cell;
auto &port = sink_cell->ports.at(sink->port);
@@ -396,7 +618,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
auto &driver = net->driver;
auto driver_cell = driver.cell;
DelayInfo comb_delay;
- ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
+ if (last_port == driver.port) {
+ // Case where we start with a STARTPOINT etc
+ comb_delay = ctx->getDelayFromNS(0);
+ } else {
+ ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
+ }
total += comb_delay.maxDelay();
log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total),
driver_cell->name.c_str(ctx), driver.port.c_str(ctx));
@@ -427,12 +654,63 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
}
last_port = sink->port;
}
+ };
+
+ for (auto &clock : clock_reports) {
log_break();
+ std::string start = clock.second.first.start.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge");
+ std::string end = clock.second.first.end.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge");
+ log_info("Critical path report for clock '%s' (%s -> %s):\n", clock.first.c_str(ctx), start.c_str(), end.c_str());
+ auto &crit_path = clock.second.second.ports;
+ print_path_report(clock.second.first, crit_path);
+ }
+
+ for (auto &xclock : xclock_paths) {
+ log_break();
+ std::string start = format_event(xclock.start);
+ std::string end = format_event(xclock.end);
+ log_info("Critical path report for cross-domain path '%s' -> '%s':\n", start.c_str(), end.c_str());
+ auto &crit_path = crit_paths.at(xclock).ports;
+ print_path_report(xclock, crit_path);
}
}
+ if (print_fmax) {
+ log_break();
+ unsigned max_width = 0;
+ for (auto &clock : clock_reports)
+ max_width = std::max<unsigned>(max_width, clock.first.str(ctx).size());
+ for (auto &clock : clock_reports) {
+ const auto &clock_name = clock.first.str(ctx);
+ const int width = max_width - clock_name.size();
+ if (ctx->nets.at(clock.first)->clkconstr) {
+ float target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay());
+ log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", clock_name.c_str(),
+ clock_fmax[clock.first], (target < clock_fmax[clock.first]) ? "PASS" : "FAIL", target);
+ } else {
+ log_info("Max frequency for clock %*s'%s': %.02f MHz\n", width, "", clock_name.c_str(), clock_fmax[clock.first]);
+ }
+ }
+ for (auto &eclock : empty_clocks) {
+ if (eclock != ctx->id("$async$"))
+ log_info("Clock '%s' has no interior paths\n", eclock.c_str(ctx));
+ }
+ log_break();
+
+ int start_field_width = 0, end_field_width = 0;
+ for (auto &xclock : xclock_paths) {
+ start_field_width = std::max((int)format_event(xclock.start).length(), start_field_width);
+ end_field_width = std::max((int)format_event(xclock.end).length(), end_field_width);
+ }
- delay_t default_slack = delay_t((1.0e9 / ctx->getDelayNS(1)) / ctx->target_freq);
- log_info("estimated Fmax = %.2f MHz\n", 1e3 / ctx->getDelayNS(default_slack - min_slack));
+ for (auto &xclock : xclock_paths) {
+ const ClockEvent &a = xclock.start;
+ const ClockEvent &b = xclock.end;
+ auto &path = crit_paths.at(xclock);
+ auto ev_a = format_event(a, start_field_width), ev_b = format_event(b, end_field_width);
+ log_info("Max delay %s -> %s: %0.02f ns\n", ev_a.c_str(), ev_b.c_str(), ctx->getDelayNS(path.path_delay));
+ }
+ log_break();
+ }
if (print_histogram && slack_histogram.size() > 0) {
unsigned num_bins = 20;
diff --git a/common/timing.h b/common/timing.h
index cfb71ae0..1fd76310 100644
--- a/common/timing.h
+++ b/common/timing.h
@@ -29,7 +29,7 @@ void assign_budget(Context *ctx, bool quiet = false);
// Perform timing analysis and print out the fmax, and optionally the
// 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
diff --git a/docs/archapi.md b/docs/archapi.md
index 40eabd9d..3c938865 100644
--- a/docs/archapi.md
+++ b/docs/archapi.md
@@ -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.
+### 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
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
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
-output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockPort is set
-to the associated clock port.
+output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockInfoCount is set
+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
--------------
diff --git a/docs/constraints.md b/docs/constraints.md
new file mode 100644
index 00000000..263df7b6
--- /dev/null
+++ b/docs/constraints.md
@@ -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)
+
diff --git a/ecp5/arch.cc b/ecp5/arch.cc
index 9001ce6c..fe6a9545 100644
--- a/ecp5/arch.cc
+++ b/ecp5/arch.cc
@@ -539,10 +539,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
return true;
}
#if 0 // FIXME
- if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {
- delay.delay = 717;
- return true;
- }
+ if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {
+ delay.delay = 717;
+ return true;
+ }
#endif
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) ||
@@ -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; };
-
+ clockInfoCount = 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_CLK || port == id_WCK)
@@ -598,13 +598,13 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
return TMG_COMB_OUTPUT;
if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
(sd1 == 1 && port == id_M1)) {
- clockPort = id_CLK;
+ clockInfoCount = 1;
return TMG_REGISTER_INPUT;
}
if (port == id_M0 || port == id_M1)
return TMG_COMB_INPUT;
if (port == id_Q0 || port == id_Q1) {
- clockPort = id_CLK;
+ clockInfoCount = 1;
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 ||
port == id_WAD3 || port == id_WRE) {
- clockPort = id_WCK;
+ clockInfoCount = 1;
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)) {
if (std::isdigit(c))
continue;
- if (c == 'A')
- clockPort = id_CLKA;
- else if (c == 'B')
- clockPort = id_CLKB;
+ if (c == 'A' || c == 'B')
+ clockInfoCount = 1;
else
NPNR_ASSERT_FALSE_STR("bad ram port");
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>> ret;
diff --git a/ecp5/arch.h b/ecp5/arch.h
index 9daae11d..aa3c5348 100644
--- a/ecp5/arch.h
+++ b/ecp5/arch.h
@@ -859,6 +859,12 @@ struct Arch : BaseCtx
delay_t getDelayEpsilon() const { return 20; }
delay_t getRipupDelayPenalty() const { return 200; }
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; }
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
// if no path exists
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
- // Get the port class, also setting clockPort if applicable
- TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const;
+ // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
+ 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
bool isGlobalNet(const NetInfo *net) const;
diff --git a/ecp5/arch_pybindings.cc b/ecp5/arch_pybindings.cc
index 9312b4ad..5e73a673 100644
--- a/ecp5/arch_pybindings.cc
+++ b/ecp5/arch_pybindings.cc
@@ -130,6 +130,10 @@ void arch_wrap_python()
"cells");
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
"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(Wire, conv_to_str<WireId>);
WRAP_RANGE(AllPip, conv_to_str<PipId>);
diff --git a/ecp5/globals.cc b/ecp5/globals.cc
index 06412fef..9b0928a4 100644
--- a/ecp5/globals.cc
+++ b/ecp5/globals.cc
@@ -350,6 +350,13 @@ class Ecp5GlobalRouter
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);
NetInfo *glbptr = glbnet.get();
ctx->nets[glbnet->name] = std::move(glbnet);
diff --git a/generic/arch.cc b/generic/arch.cc
index 4f2e07a2..77417d27 100644
--- a/generic/arch.cc
+++ b/generic/arch.cc
@@ -463,11 +463,16 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
}
// 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;
}
+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::isBelLocationValid(BelId bel) const { return true; }
diff --git a/generic/arch.h b/generic/arch.h
index 9311464e..dc4258cc 100644
--- a/generic/arch.h
+++ b/generic/arch.h
@@ -211,6 +211,14 @@ struct Arch : BaseCtx
delay_t getDelayEpsilon() const { return 0.01; }
delay_t getRipupDelayPenalty() const { return 1.0; }
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; }
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;
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
- // Get the port class, also setting clockPort if applicable
- TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const;
+ // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
+ 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 isBelLocationValid(BelId bel) const;
diff --git a/ice40/arch.cc b/ice40/arch.cc
index b5e20566..2a9e167b 100644
--- a/ice40/arch.cc
+++ b/ice40/arch.cc
@@ -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
-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 (port == id_CLK)
return TMG_CLOCK_INPUT;
@@ -870,18 +871,15 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
if (cell->lcInfo.inputCount == 0)
return TMG_IGNORE;
if (cell->lcInfo.dffEnable) {
- clockPort = id_CLK;
+ clockInfoCount = 1;
return TMG_REGISTER_OUTPUT;
- }
- else
+ } else
return TMG_COMB_OUTPUT;
- }
- else {
+ } else {
if (cell->lcInfo.dffEnable) {
- clockPort = id_CLK;
+ clockInfoCount = 1;
return TMG_REGISTER_INPUT;
- }
- else
+ } else
return TMG_COMB_INPUT;
}
} 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)
return TMG_CLOCK_INPUT;
- if (port.str(this)[0] == 'R')
- clockPort = id_RCLK;
- else
- clockPort = id_WCLK;
+ clockInfoCount = 1;
if (cell->ports.at(port).type == PORT_OUT)
return TMG_REGISTER_OUTPUT;
else
return TMG_REGISTER_INPUT;
} else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) {
- clockPort = id_CLK;
- if (port == id_CLK)
+ if (port == id_CLK || port == id_CLOCK)
return TMG_CLOCK_INPUT;
- else if (cell->ports.at(port).type == PORT_OUT)
- return TMG_REGISTER_OUTPUT;
- else
- return TMG_REGISTER_INPUT;
+ else {
+ clockInfoCount = 1;
+ if (cell->ports.at(port).type == PORT_OUT)
+ return TMG_REGISTER_OUTPUT;
+ else
+ return TMG_REGISTER_INPUT;
+ }
} else if (cell->type == id_SB_IO) {
if (port == id_D_IN_0 || port == id_D_IN_1)
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));
}
+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
{
if (net == nullptr)
diff --git a/ice40/arch.h b/ice40/arch.h
index f7d31b0e..836dc46e 100644
--- a/ice40/arch.h
+++ b/ice40/arch.h
@@ -796,6 +796,12 @@ struct Arch : BaseCtx
delay_t getDelayEpsilon() const { return 20; }
delay_t getRipupDelayPenalty() const { return 200; }
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; }
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
// if no path exists
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
- // Get the port class, also setting clockDomain if applicable
- TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockDomain) const;
+ // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
+ 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
bool isGlobalNet(const NetInfo *net) const;
diff --git a/ice40/arch_pybindings.cc b/ice40/arch_pybindings.cc
index f1639ba6..3fafb1f6 100644
--- a/ice40/arch_pybindings.cc
+++ b/ice40/arch_pybindings.cc
@@ -140,6 +140,10 @@ void arch_wrap_python()
"cells");
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
"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(Wire, conv_to_str<WireId>);
WRAP_RANGE(AllPip, conv_to_str<PipId>);
diff --git a/ice40/pack.cc b/ice40/pack.cc
index edd12f92..7a27d505 100644
--- a/ice40/pack.cc
+++ b/ice40/pack.cc
@@ -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)
{
- 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::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;
+
+ 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->cells[gb->name] = std::move(gb);
}