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);
}
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

View File

@ -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

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);
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);

View File

@ -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;

View File

@ -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,18 +244,28 @@ 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);
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) {
IdString clockPort;
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort);
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) {
} else {
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;
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)
@ -195,7 +275,7 @@ struct Timing
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 &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
@ -207,44 +287,96 @@ struct Timing
}
}
}
}
const NetInfo *crit_net = nullptr;
std::unordered_map<ClockPair, std::pair<delay_t, NetInfo *>> crit_nets;
// Now go backwards topographically to determine the minimum path slack, and to distribute all path slack evenly
// between all nets on the path
for (auto net : boost::adaptors::reverse(topographical_order)) {
auto &nd = net_data.at(net);
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;
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);
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;
auto path_budget = clk_period - (net_arrival + net_delay);
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);
net_min_remaining_budget =
std::min(net_min_remaining_budget, path_budget - budget_share);
}
if (path_budget < min_slack) {
if (path_budget < min_slack)
min_slack = path_budget;
if (crit_path) {
crit_path->clear();
crit_path->push_back(&usr);
crit_net = net;
}
}
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)
@ -253,10 +385,16 @@ struct Timing
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;
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);
net_min_remaining_budget =
std::min(net_min_remaining_budget, path_budget - budget_share);
}
}
}
}
}
@ -264,6 +402,9 @@ struct Timing
if (crit_path) {
// Walk backwards from the most critical net
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();
@ -278,19 +419,23 @@ struct Timing
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)
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_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;
@ -298,13 +443,14 @@ struct Timing
// 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);
cp_ports.push_back(&usr);
break;
}
}
crit_net = crit_ipin->net;
}
std::reverse(crit_path->begin(), crit_path->end());
std::reverse(cp_ports.begin(), cp_ports.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;
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);
}
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) {
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);
}
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;

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
// 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

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.
### 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
--------------

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

@ -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;

View File

@ -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;

View File

@ -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>);

View File

@ -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);

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
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; }

View File

@ -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;

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
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)
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)

View File

@ -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;

View File

@ -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>);

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)
{
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);
}