Merge pull request #103 from YosysHQ/timingapi
Timing constraints API, multiple clock domains
This commit is contained in:
commit
9472b6d78f
@ -53,6 +53,107 @@ void IdString::initialize_add(const BaseCtx *ctx, const char *s, int idx)
|
|||||||
ctx->idstring_idx_to_str->push_back(&insert_rc.first->first);
|
ctx->idstring_idx_to_str->push_back(&insert_rc.first->first);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimingConstrObjectId BaseCtx::timingWildcardObject()
|
||||||
|
{
|
||||||
|
TimingConstrObjectId id;
|
||||||
|
id.index = 0;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimingConstrObjectId BaseCtx::timingClockDomainObject(NetInfo *clockDomain)
|
||||||
|
{
|
||||||
|
NPNR_ASSERT(clockDomain->clkconstr != nullptr);
|
||||||
|
if (clockDomain->clkconstr->domain_tmg_id != TimingConstrObjectId()) {
|
||||||
|
return clockDomain->clkconstr->domain_tmg_id;
|
||||||
|
} else {
|
||||||
|
TimingConstraintObject obj;
|
||||||
|
TimingConstrObjectId id;
|
||||||
|
id.index = int(constraintObjects.size());
|
||||||
|
obj.id = id;
|
||||||
|
obj.type = TimingConstraintObject::CLOCK_DOMAIN;
|
||||||
|
obj.entity = clockDomain->name;
|
||||||
|
clockDomain->clkconstr->domain_tmg_id = id;
|
||||||
|
constraintObjects.push_back(obj);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimingConstrObjectId BaseCtx::timingNetObject(NetInfo *net)
|
||||||
|
{
|
||||||
|
if (net->tmg_id != TimingConstrObjectId()) {
|
||||||
|
return net->tmg_id;
|
||||||
|
} else {
|
||||||
|
TimingConstraintObject obj;
|
||||||
|
TimingConstrObjectId id;
|
||||||
|
id.index = int(constraintObjects.size());
|
||||||
|
obj.id = id;
|
||||||
|
obj.type = TimingConstraintObject::NET;
|
||||||
|
obj.entity = net->name;
|
||||||
|
constraintObjects.push_back(obj);
|
||||||
|
net->tmg_id = id;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimingConstrObjectId BaseCtx::timingCellObject(CellInfo *cell)
|
||||||
|
{
|
||||||
|
if (cell->tmg_id != TimingConstrObjectId()) {
|
||||||
|
return cell->tmg_id;
|
||||||
|
} else {
|
||||||
|
TimingConstraintObject obj;
|
||||||
|
TimingConstrObjectId id;
|
||||||
|
id.index = int(constraintObjects.size());
|
||||||
|
obj.id = id;
|
||||||
|
obj.type = TimingConstraintObject::CELL;
|
||||||
|
obj.entity = cell->name;
|
||||||
|
constraintObjects.push_back(obj);
|
||||||
|
cell->tmg_id = id;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimingConstrObjectId BaseCtx::timingPortObject(CellInfo *cell, IdString port)
|
||||||
|
{
|
||||||
|
if (cell->ports.at(port).tmg_id != TimingConstrObjectId()) {
|
||||||
|
return cell->ports.at(port).tmg_id;
|
||||||
|
} else {
|
||||||
|
TimingConstraintObject obj;
|
||||||
|
TimingConstrObjectId id;
|
||||||
|
id.index = int(constraintObjects.size());
|
||||||
|
obj.id = id;
|
||||||
|
obj.type = TimingConstraintObject::CELL_PORT;
|
||||||
|
obj.entity = cell->name;
|
||||||
|
obj.port = port;
|
||||||
|
constraintObjects.push_back(obj);
|
||||||
|
cell->ports.at(port).tmg_id = id;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseCtx::addConstraint(std::unique_ptr<TimingConstraint> constr)
|
||||||
|
{
|
||||||
|
for (auto fromObj : constr->from)
|
||||||
|
constrsFrom.emplace(fromObj, constr.get());
|
||||||
|
for (auto toObj : constr->to)
|
||||||
|
constrsTo.emplace(toObj, constr.get());
|
||||||
|
IdString name = constr->name;
|
||||||
|
constraints[name] = std::move(constr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseCtx::removeConstraint(IdString constrName)
|
||||||
|
{
|
||||||
|
TimingConstraint *constr = constraints[constrName].get();
|
||||||
|
for (auto fromObj : constr->from) {
|
||||||
|
auto fromConstrs = constrsFrom.equal_range(fromObj);
|
||||||
|
constrsFrom.erase(std::find(fromConstrs.first, fromConstrs.second, std::make_pair(fromObj, constr)));
|
||||||
|
}
|
||||||
|
for (auto toObj : constr->to) {
|
||||||
|
auto toConstrs = constrsFrom.equal_range(toObj);
|
||||||
|
constrsFrom.erase(std::find(toConstrs.first, toConstrs.second, std::make_pair(toObj, constr)));
|
||||||
|
}
|
||||||
|
constraints.erase(constrName);
|
||||||
|
}
|
||||||
|
|
||||||
const char *BaseCtx::nameOfBel(BelId bel) const
|
const char *BaseCtx::nameOfBel(BelId bel) const
|
||||||
{
|
{
|
||||||
const Context *ctx = getCtx();
|
const Context *ctx = getCtx();
|
||||||
@ -306,4 +407,14 @@ void Context::check() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BaseCtx::addClock(IdString net, float freq)
|
||||||
|
{
|
||||||
|
log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq);
|
||||||
|
std::unique_ptr<ClockConstraint> cc(new ClockConstraint());
|
||||||
|
cc->period = getCtx()->getDelayFromNS(1000 / freq);
|
||||||
|
cc->high = getCtx()->getDelayFromNS(500 / freq);
|
||||||
|
cc->low = getCtx()->getDelayFromNS(500 / freq);
|
||||||
|
nets.at(net)->clkconstr = std::move(cc);
|
||||||
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
116
common/nextpnr.h
116
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); }
|
bool operator!=(const Loc &other) const { return (x != other.x) || (y != other.y) || (z != other.z); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TimingConstrObjectId
|
||||||
|
{
|
||||||
|
int32_t index = -1;
|
||||||
|
|
||||||
|
bool operator==(const TimingConstrObjectId &other) const { return index == other.index; }
|
||||||
|
bool operator!=(const TimingConstrObjectId &other) const { return index != other.index; }
|
||||||
|
};
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
@ -208,6 +216,15 @@ template <> struct hash<NEXTPNR_NAMESPACE_PREFIX Loc>
|
|||||||
return seed;
|
return seed;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX TimingConstrObjectId>
|
||||||
|
{
|
||||||
|
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX TimingConstrObjectId &obj) const noexcept
|
||||||
|
{
|
||||||
|
return hash<int>()(obj.index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
#include "archdefs.h"
|
#include "archdefs.h"
|
||||||
@ -266,6 +283,8 @@ struct PipMap
|
|||||||
PlaceStrength strength = STRENGTH_NONE;
|
PlaceStrength strength = STRENGTH_NONE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ClockConstraint;
|
||||||
|
|
||||||
struct NetInfo : ArchNetInfo
|
struct NetInfo : ArchNetInfo
|
||||||
{
|
{
|
||||||
IdString name;
|
IdString name;
|
||||||
@ -278,6 +297,10 @@ struct NetInfo : ArchNetInfo
|
|||||||
// wire -> uphill_pip
|
// wire -> uphill_pip
|
||||||
std::unordered_map<WireId, PipMap> wires;
|
std::unordered_map<WireId, PipMap> wires;
|
||||||
|
|
||||||
|
std::unique_ptr<ClockConstraint> clkconstr;
|
||||||
|
|
||||||
|
TimingConstrObjectId tmg_id;
|
||||||
|
|
||||||
Region *region = nullptr;
|
Region *region = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -293,6 +316,7 @@ struct PortInfo
|
|||||||
IdString name;
|
IdString name;
|
||||||
NetInfo *net;
|
NetInfo *net;
|
||||||
PortType type;
|
PortType type;
|
||||||
|
TimingConstrObjectId tmg_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CellInfo : ArchCellInfo
|
struct CellInfo : ArchCellInfo
|
||||||
@ -320,6 +344,7 @@ struct CellInfo : ArchCellInfo
|
|||||||
// parent.[xyz] := 0 when (constr_parent == nullptr)
|
// parent.[xyz] := 0 when (constr_parent == nullptr)
|
||||||
|
|
||||||
Region *region = nullptr;
|
Region *region = nullptr;
|
||||||
|
TimingConstrObjectId tmg_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum TimingPortClass
|
enum TimingPortClass
|
||||||
@ -335,6 +360,68 @@ enum TimingPortClass
|
|||||||
TMG_IGNORE, // Asynchronous to all clocks, "don't care", and should be ignored (false path) for analysis
|
TMG_IGNORE, // Asynchronous to all clocks, "don't care", and should be ignored (false path) for analysis
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ClockEdge
|
||||||
|
{
|
||||||
|
RISING_EDGE,
|
||||||
|
FALLING_EDGE
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TimingClockingInfo
|
||||||
|
{
|
||||||
|
IdString clock_port; // Port name of clock domain
|
||||||
|
ClockEdge edge;
|
||||||
|
DelayInfo setup, hold; // Input timing checks
|
||||||
|
DelayInfo clockToQ; // Output clock-to-Q time
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClockConstraint
|
||||||
|
{
|
||||||
|
DelayInfo high;
|
||||||
|
DelayInfo low;
|
||||||
|
DelayInfo period;
|
||||||
|
|
||||||
|
TimingConstrObjectId domain_tmg_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TimingConstraintObject
|
||||||
|
{
|
||||||
|
TimingConstrObjectId id;
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ANYTHING,
|
||||||
|
CLOCK_DOMAIN,
|
||||||
|
NET,
|
||||||
|
CELL,
|
||||||
|
CELL_PORT
|
||||||
|
} type;
|
||||||
|
IdString entity; // Name of clock net; net or cell
|
||||||
|
IdString port; // Name of port on a cell
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TimingConstraint
|
||||||
|
{
|
||||||
|
IdString name;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
FALSE_PATH,
|
||||||
|
MIN_DELAY,
|
||||||
|
MAX_DELAY,
|
||||||
|
MULTICYCLE,
|
||||||
|
} type;
|
||||||
|
|
||||||
|
delay_t value;
|
||||||
|
|
||||||
|
std::unordered_set<TimingConstrObjectId> from;
|
||||||
|
std::unordered_set<TimingConstrObjectId> to;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool operator==(const std::pair<const TimingConstrObjectId, TimingConstraint *> &a,
|
||||||
|
const std::pair<TimingConstrObjectId, TimingConstraint *> &b)
|
||||||
|
{
|
||||||
|
return a.first == b.first && a.second == b.second;
|
||||||
|
}
|
||||||
|
|
||||||
struct DeterministicRNG
|
struct DeterministicRNG
|
||||||
{
|
{
|
||||||
uint64_t rngstate;
|
uint64_t rngstate;
|
||||||
@ -431,6 +518,11 @@ struct BaseCtx
|
|||||||
idstring_idx_to_str = new std::vector<const std::string *>;
|
idstring_idx_to_str = new std::vector<const std::string *>;
|
||||||
IdString::initialize_add(this, "", 0);
|
IdString::initialize_add(this, "", 0);
|
||||||
IdString::initialize_arch(this);
|
IdString::initialize_arch(this);
|
||||||
|
|
||||||
|
TimingConstraintObject wildcard;
|
||||||
|
wildcard.id.index = 0;
|
||||||
|
wildcard.type = TimingConstraintObject::ANYTHING;
|
||||||
|
constraintObjects.push_back(wildcard);
|
||||||
}
|
}
|
||||||
|
|
||||||
~BaseCtx()
|
~BaseCtx()
|
||||||
@ -524,6 +616,30 @@ struct BaseCtx
|
|||||||
void refreshUiPip(PipId pip) { pipUiReload.insert(pip); }
|
void refreshUiPip(PipId pip) { pipUiReload.insert(pip); }
|
||||||
|
|
||||||
void refreshUiGroup(GroupId group) { groupUiReload.insert(group); }
|
void refreshUiGroup(GroupId group) { groupUiReload.insert(group); }
|
||||||
|
|
||||||
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
|
// Timing Constraint API
|
||||||
|
|
||||||
|
// constraint name -> constraint
|
||||||
|
std::unordered_map<IdString, std::unique_ptr<TimingConstraint>> constraints;
|
||||||
|
// object ID -> object
|
||||||
|
std::vector<TimingConstraintObject> constraintObjects;
|
||||||
|
// object ID -> constraint
|
||||||
|
std::unordered_multimap<TimingConstrObjectId, TimingConstraint *> constrsFrom;
|
||||||
|
std::unordered_multimap<TimingConstrObjectId, TimingConstraint *> constrsTo;
|
||||||
|
|
||||||
|
TimingConstrObjectId timingWildcardObject();
|
||||||
|
TimingConstrObjectId timingClockDomainObject(NetInfo *clockDomain);
|
||||||
|
TimingConstrObjectId timingNetObject(NetInfo *net);
|
||||||
|
TimingConstrObjectId timingCellObject(CellInfo *cell);
|
||||||
|
TimingConstrObjectId timingPortObject(CellInfo *cell, IdString port);
|
||||||
|
|
||||||
|
void addConstraint(std::unique_ptr<TimingConstraint> constr);
|
||||||
|
void removeConstraint(IdString constrName);
|
||||||
|
|
||||||
|
// Intended to simplify Python API
|
||||||
|
void addClock(IdString net, float freq);
|
||||||
};
|
};
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -36,8 +36,8 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type
|
|||||||
bool driver_gb = ctx->getBelGlobalBuf(driver_cell->bel);
|
bool driver_gb = ctx->getBelGlobalBuf(driver_cell->bel);
|
||||||
if (driver_gb)
|
if (driver_gb)
|
||||||
return 0;
|
return 0;
|
||||||
IdString clock_port;
|
int clock_count;
|
||||||
bool timing_driven = ctx->timing_driven && type == MetricType::COST && ctx->getPortTimingClass(driver_cell, net->driver.port, clock_port) != TMG_IGNORE;
|
bool timing_driven = ctx->timing_driven && type == MetricType::COST && ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE;
|
||||||
delay_t negative_slack = 0;
|
delay_t negative_slack = 0;
|
||||||
delay_t worst_slack = std::numeric_limits<delay_t>::max();
|
delay_t worst_slack = std::numeric_limits<delay_t>::max();
|
||||||
Loc driver_loc = ctx->getBelLocation(driver_cell->bel);
|
Loc driver_loc = ctx->getBelLocation(driver_cell->bel);
|
||||||
|
@ -808,7 +808,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
||||||
timing_analysis(ctx, true /* slack_histogram */, true /* print_path */);
|
timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */);
|
||||||
|
|
||||||
ctx->unlock();
|
ctx->unlock();
|
||||||
return true;
|
return true;
|
||||||
|
398
common/timing.cc
398
common/timing.cc
@ -22,6 +22,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <boost/range/adaptor/reversed.hpp>
|
#include <boost/range/adaptor/reversed.hpp>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <map>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@ -29,17 +30,72 @@
|
|||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct ClockEvent
|
||||||
|
{
|
||||||
|
IdString clock;
|
||||||
|
ClockEdge edge;
|
||||||
|
|
||||||
|
bool operator==(const ClockEvent &other) const { return clock == other.clock && edge == other.edge; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClockPair
|
||||||
|
{
|
||||||
|
ClockEvent start, end;
|
||||||
|
|
||||||
|
bool operator==(const ClockPair &other) const { return start == other.start && end == other.end; }
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_END
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>
|
||||||
|
{
|
||||||
|
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockEvent &obj) const noexcept
|
||||||
|
{
|
||||||
|
std::size_t seed = 0;
|
||||||
|
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(obj.clock));
|
||||||
|
boost::hash_combine(seed, hash<int>()(int(obj.edge)));
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockPair>
|
||||||
|
{
|
||||||
|
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockPair &obj) const noexcept
|
||||||
|
{
|
||||||
|
std::size_t seed = 0;
|
||||||
|
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start));
|
||||||
|
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start));
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
typedef std::vector<const PortRef *> PortRefVector;
|
typedef std::vector<const PortRef *> PortRefVector;
|
||||||
typedef std::map<int, unsigned> DelayFrequency;
|
typedef std::map<int, unsigned> DelayFrequency;
|
||||||
|
|
||||||
|
struct CriticalPath
|
||||||
|
{
|
||||||
|
PortRefVector ports;
|
||||||
|
delay_t path_delay;
|
||||||
|
delay_t path_period;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::unordered_map<ClockPair, CriticalPath> CriticalPathMap;
|
||||||
|
|
||||||
struct Timing
|
struct Timing
|
||||||
{
|
{
|
||||||
Context *ctx;
|
Context *ctx;
|
||||||
bool net_delays;
|
bool net_delays;
|
||||||
bool update;
|
bool update;
|
||||||
delay_t min_slack;
|
delay_t min_slack;
|
||||||
PortRefVector *crit_path;
|
CriticalPathMap *crit_path;
|
||||||
DelayFrequency *slack_histogram;
|
DelayFrequency *slack_histogram;
|
||||||
|
IdString async_clock;
|
||||||
|
|
||||||
struct TimingData
|
struct TimingData
|
||||||
{
|
{
|
||||||
@ -49,23 +105,24 @@ struct Timing
|
|||||||
unsigned max_path_length = 0;
|
unsigned max_path_length = 0;
|
||||||
delay_t min_remaining_budget;
|
delay_t min_remaining_budget;
|
||||||
bool false_startpoint = false;
|
bool false_startpoint = false;
|
||||||
|
std::unordered_map<ClockEvent, delay_t> arrival_time;
|
||||||
};
|
};
|
||||||
|
|
||||||
Timing(Context *ctx, bool net_delays, bool update, PortRefVector *crit_path = nullptr,
|
Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr,
|
||||||
DelayFrequency *slack_histogram = nullptr)
|
DelayFrequency *slack_histogram = nullptr)
|
||||||
: ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq),
|
: ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq),
|
||||||
crit_path(crit_path), slack_histogram(slack_histogram)
|
crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$"))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
delay_t walk_paths()
|
delay_t walk_paths()
|
||||||
{
|
{
|
||||||
const auto clk_period = delay_t(1.0e12 / ctx->target_freq);
|
const auto clk_period = ctx->getDelayFromNS(1.0e9 / ctx->target_freq).maxDelay();
|
||||||
|
|
||||||
// First, compute the topographical order of nets to walk through the circuit, assuming it is a _acyclic_ graph
|
// First, compute the topographical order of nets to walk through the circuit, assuming it is a _acyclic_ graph
|
||||||
// TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops
|
// TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops
|
||||||
std::vector<NetInfo *> topographical_order;
|
std::vector<NetInfo *> topographical_order;
|
||||||
std::unordered_map<const NetInfo *, TimingData> net_data;
|
std::unordered_map<const NetInfo *, std::unordered_map<ClockEvent, TimingData>> net_data;
|
||||||
// In lieu of deleting edges from the graph, simply count the number of fanins to each output port
|
// In lieu of deleting edges from the graph, simply count the number of fanins to each output port
|
||||||
std::unordered_map<const PortInfo *, unsigned> port_fanin;
|
std::unordered_map<const PortInfo *, unsigned> port_fanin;
|
||||||
|
|
||||||
@ -84,22 +141,34 @@ struct Timing
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto o : output_ports) {
|
for (auto o : output_ports) {
|
||||||
IdString clockPort;
|
int clocks = 0;
|
||||||
TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clockPort);
|
TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clocks);
|
||||||
// If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing
|
// If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing
|
||||||
// start-point
|
// start-point
|
||||||
if (portClass == TMG_REGISTER_OUTPUT) {
|
if (portClass == TMG_REGISTER_OUTPUT) {
|
||||||
DelayInfo clkToQ;
|
|
||||||
ctx->getCellDelay(cell.second.get(), clockPort, o->name, clkToQ);
|
|
||||||
topographical_order.emplace_back(o->net);
|
topographical_order.emplace_back(o->net);
|
||||||
net_data.emplace(o->net, TimingData{clkToQ.maxDelay()});
|
for (int i = 0; i < clocks; i++) {
|
||||||
|
TimingClockingInfo clkInfo = ctx->getPortClockingInfo(cell.second.get(), o->name, i);
|
||||||
|
const NetInfo *clknet = get_net_or_empty(cell.second.get(), clkInfo.clock_port);
|
||||||
|
IdString clksig = clknet ? clknet->name : async_clock;
|
||||||
|
net_data[o->net][ClockEvent{clksig, clknet ? clkInfo.edge : RISING_EDGE}] =
|
||||||
|
TimingData{clkInfo.clockToQ.maxDelay()};
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) {
|
if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) {
|
||||||
topographical_order.emplace_back(o->net);
|
topographical_order.emplace_back(o->net);
|
||||||
TimingData td;
|
TimingData td;
|
||||||
td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE);
|
td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE);
|
||||||
net_data.emplace(o->net, std::move(td));
|
td.max_arrival = 0;
|
||||||
|
net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't analyse paths from a clock input to other pins - they will be considered by the
|
||||||
|
// special-case handling register input/output class ports
|
||||||
|
if (portClass == TMG_CLOCK_INPUT)
|
||||||
|
continue;
|
||||||
|
|
||||||
// Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and
|
// Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and
|
||||||
// the current output port, increment fanin counter
|
// the current output port, increment fanin counter
|
||||||
for (auto i : input_ports) {
|
for (auto i : input_ports) {
|
||||||
@ -120,14 +189,15 @@ struct Timing
|
|||||||
queue.pop_front();
|
queue.pop_front();
|
||||||
|
|
||||||
for (auto &usr : net->users) {
|
for (auto &usr : net->users) {
|
||||||
IdString clockPort;
|
int user_clocks;
|
||||||
TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort);
|
TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, user_clocks);
|
||||||
if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT)
|
if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT)
|
||||||
continue;
|
continue;
|
||||||
for (auto &port : usr.cell->ports) {
|
for (auto &port : usr.cell->ports) {
|
||||||
if (port.second.type != PORT_OUT || !port.second.net)
|
if (port.second.type != PORT_OUT || !port.second.net)
|
||||||
continue;
|
continue;
|
||||||
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, clockPort);
|
int port_clocks;
|
||||||
|
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, port_clocks);
|
||||||
|
|
||||||
// Skip if this is a clocked output (but allow non-clocked ones)
|
// Skip if this is a clocked output (but allow non-clocked ones)
|
||||||
if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE ||
|
if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE ||
|
||||||
@ -174,18 +244,28 @@ struct Timing
|
|||||||
|
|
||||||
// Go forwards topographically to find the maximum arrival time and max path length for each net
|
// Go forwards topographically to find the maximum arrival time and max path length for each net
|
||||||
for (auto net : topographical_order) {
|
for (auto net : topographical_order) {
|
||||||
auto &nd = net_data.at(net);
|
if (!net_data.count(net))
|
||||||
|
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_arrival = nd.max_arrival;
|
||||||
const auto net_length_plus_one = nd.max_path_length + 1;
|
const auto net_length_plus_one = nd.max_path_length + 1;
|
||||||
nd.min_remaining_budget = clk_period;
|
nd.min_remaining_budget = clk_period;
|
||||||
for (auto &usr : net->users) {
|
for (auto &usr : net->users) {
|
||||||
IdString clockPort;
|
int port_clocks;
|
||||||
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort);
|
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
|
||||||
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) {
|
|
||||||
} else {
|
|
||||||
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
|
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
|
||||||
auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
|
|
||||||
auto usr_arrival = net_arrival + net_delay;
|
auto usr_arrival = net_arrival + net_delay;
|
||||||
|
|
||||||
|
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
|
// Iterate over all output ports on the same cell as the sink
|
||||||
for (auto port : usr.cell->ports) {
|
for (auto port : usr.cell->ports) {
|
||||||
if (port.second.type != PORT_OUT || !port.second.net)
|
if (port.second.type != PORT_OUT || !port.second.net)
|
||||||
@ -195,7 +275,7 @@ struct Timing
|
|||||||
bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
|
bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
|
||||||
if (!is_path)
|
if (!is_path)
|
||||||
continue;
|
continue;
|
||||||
auto &data = net_data[port.second.net];
|
auto &data = net_data[port.second.net][start_clk];
|
||||||
auto &arrival = data.max_arrival;
|
auto &arrival = data.max_arrival;
|
||||||
arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay());
|
arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay());
|
||||||
if (!budget_override) { // Do not increment path length if budget overriden since it doesn't
|
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
|
// Now go backwards topographically to determine the minimum path slack, and to distribute all path slack evenly
|
||||||
// between all nets on the path
|
// between all nets on the path
|
||||||
for (auto net : boost::adaptors::reverse(topographical_order)) {
|
for (auto net : boost::adaptors::reverse(topographical_order)) {
|
||||||
auto &nd = net_data.at(net);
|
if (!net_data.count(net))
|
||||||
|
continue;
|
||||||
|
auto &nd_map = net_data.at(net);
|
||||||
|
for (auto &startdomain : nd_map) {
|
||||||
|
auto &nd = startdomain.second;
|
||||||
// Ignore false startpoints
|
// 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;
|
const delay_t net_length_plus_one = nd.max_path_length + 1;
|
||||||
auto &net_min_remaining_budget = nd.min_remaining_budget;
|
auto &net_min_remaining_budget = nd.min_remaining_budget;
|
||||||
for (auto &usr : net->users) {
|
for (auto &usr : net->users) {
|
||||||
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
|
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
|
||||||
auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
|
auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
|
||||||
IdString associatedClock;
|
int port_clocks;
|
||||||
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, associatedClock);
|
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
|
||||||
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) {
|
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 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) {
|
if (update) {
|
||||||
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
|
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
|
||||||
usr.budget = std::min(usr.budget, net_delay + budget_share);
|
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;
|
min_slack = path_budget;
|
||||||
if (crit_path) {
|
|
||||||
crit_path->clear();
|
|
||||||
crit_path->push_back(&usr);
|
|
||||||
crit_net = net;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (slack_histogram) {
|
if (slack_histogram) {
|
||||||
int slack_ps = ctx->getDelayNS(path_budget) * 1000;
|
int slack_ps = ctx->getDelayNS(path_budget) * 1000;
|
||||||
(*slack_histogram)[slack_ps]++;
|
(*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) {
|
} else if (update) {
|
||||||
|
|
||||||
// Iterate over all output ports on the same cell as the sink
|
// Iterate over all output ports on the same cell as the sink
|
||||||
for (const auto &port : usr.cell->ports) {
|
for (const auto &port : usr.cell->ports) {
|
||||||
if (port.second.type != PORT_OUT || !port.second.net)
|
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);
|
bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
|
||||||
if (!is_path)
|
if (!is_path)
|
||||||
continue;
|
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;
|
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
|
||||||
usr.budget = std::min(usr.budget, net_delay + budget_share);
|
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) {
|
if (crit_path) {
|
||||||
// Walk backwards from the most critical net
|
// 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) {
|
while (crit_net) {
|
||||||
const PortInfo *crit_ipin = nullptr;
|
const PortInfo *crit_ipin = nullptr;
|
||||||
delay_t max_arrival = std::numeric_limits<delay_t>::min();
|
delay_t max_arrival = std::numeric_limits<delay_t>::min();
|
||||||
@ -278,19 +419,23 @@ struct Timing
|
|||||||
if (!is_path)
|
if (!is_path)
|
||||||
continue;
|
continue;
|
||||||
// If input port is influenced by a clock, skip
|
// If input port is influenced by a clock, skip
|
||||||
IdString portClock;
|
int port_clocks;
|
||||||
TimingPortClass portClass = ctx->getPortTimingClass(crit_net->driver.cell, port.first, portClock);
|
TimingPortClass portClass =
|
||||||
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT ||
|
ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
|
||||||
portClass == TMG_IGNORE)
|
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT ||
|
||||||
|
portClass == TMG_ENDPOINT || portClass == TMG_IGNORE)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// And find the fanin net with the latest arrival time
|
// And find the fanin net with the latest arrival time
|
||||||
const auto net_arrival = net_data.at(port.second.net).max_arrival;
|
if (net_data.count(port.second.net) &&
|
||||||
|
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) {
|
if (net_arrival > max_arrival) {
|
||||||
max_arrival = net_arrival;
|
max_arrival = net_arrival;
|
||||||
crit_ipin = &port.second;
|
crit_ipin = &port.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!crit_ipin)
|
if (!crit_ipin)
|
||||||
break;
|
break;
|
||||||
@ -298,13 +443,14 @@ struct Timing
|
|||||||
// Now convert PortInfo* into a PortRef*
|
// Now convert PortInfo* into a PortRef*
|
||||||
for (auto &usr : crit_ipin->net->users) {
|
for (auto &usr : crit_ipin->net->users) {
|
||||||
if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crit_net = crit_ipin->net;
|
crit_net = crit_ipin->net;
|
||||||
}
|
}
|
||||||
std::reverse(crit_path->begin(), crit_path->end());
|
std::reverse(cp_ports.begin(), cp_ports.end());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return min_slack;
|
return min_slack;
|
||||||
}
|
}
|
||||||
@ -365,30 +511,106 @@ void assign_budget(Context *ctx, bool quiet)
|
|||||||
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
||||||
}
|
}
|
||||||
|
|
||||||
void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
|
void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path)
|
||||||
{
|
{
|
||||||
PortRefVector crit_path;
|
auto format_event = [ctx](const ClockEvent &e, int field_width = 0) {
|
||||||
|
std::string value;
|
||||||
|
if (e.clock == ctx->id("$async$"))
|
||||||
|
value = std::string("<async>");
|
||||||
|
else
|
||||||
|
value = (e.edge == FALLING_EDGE ? std::string("negedge ") : std::string("posedge ")) + e.clock.str(ctx);
|
||||||
|
if (int(value.length()) < field_width)
|
||||||
|
value.insert(value.length(), field_width - int(value.length()), ' ');
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
CriticalPathMap crit_paths;
|
||||||
DelayFrequency slack_histogram;
|
DelayFrequency slack_histogram;
|
||||||
|
|
||||||
Timing timing(ctx, true /* net_delays */, false /* update */, print_path ? &crit_path : nullptr,
|
Timing timing(ctx, true /* net_delays */, false /* update */, (print_path || print_fmax) ? &crit_paths : nullptr,
|
||||||
print_histogram ? &slack_histogram : nullptr);
|
print_histogram ? &slack_histogram : nullptr);
|
||||||
auto min_slack = timing.walk_paths();
|
timing.walk_paths();
|
||||||
|
std::map<IdString, std::pair<ClockPair, CriticalPath>> clock_reports;
|
||||||
|
std::map<IdString, double> clock_fmax;
|
||||||
|
std::vector<ClockPair> xclock_paths;
|
||||||
|
std::set<IdString> empty_clocks; // set of clocks with no interior paths
|
||||||
|
if (print_path || print_fmax) {
|
||||||
|
for (auto path : crit_paths) {
|
||||||
|
const ClockEvent &a = path.first.start;
|
||||||
|
const ClockEvent &b = path.first.end;
|
||||||
|
empty_clocks.insert(a.clock);
|
||||||
|
empty_clocks.insert(b.clock);
|
||||||
|
}
|
||||||
|
for (auto path : crit_paths) {
|
||||||
|
const ClockEvent &a = path.first.start;
|
||||||
|
const ClockEvent &b = path.first.end;
|
||||||
|
if (a.clock != b.clock || a.clock == ctx->id("$async$"))
|
||||||
|
continue;
|
||||||
|
double Fmax;
|
||||||
|
empty_clocks.erase(a.clock);
|
||||||
|
if (a.edge == b.edge)
|
||||||
|
Fmax = 1000 / ctx->getDelayNS(path.second.path_delay);
|
||||||
|
else
|
||||||
|
Fmax = 500 / ctx->getDelayNS(path.second.path_delay);
|
||||||
|
if (!clock_fmax.count(a.clock) || Fmax < clock_fmax.at(a.clock)) {
|
||||||
|
clock_reports[a.clock] = path;
|
||||||
|
clock_fmax[a.clock] = Fmax;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &path : crit_paths) {
|
||||||
|
const ClockEvent &a = path.first.start;
|
||||||
|
const ClockEvent &b = path.first.end;
|
||||||
|
if (a.clock == b.clock && a.clock != ctx->id("$async$"))
|
||||||
|
continue;
|
||||||
|
xclock_paths.push_back(path.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clock_reports.empty()) {
|
||||||
|
log_warning("No clocks found in design");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(xclock_paths.begin(), xclock_paths.end(), [ctx](const ClockPair &a, const ClockPair &b) {
|
||||||
|
if (a.start.clock.str(ctx) < b.start.clock.str(ctx))
|
||||||
|
return true;
|
||||||
|
if (a.start.clock.str(ctx) > b.start.clock.str(ctx))
|
||||||
|
return false;
|
||||||
|
if (a.start.edge < b.start.edge)
|
||||||
|
return true;
|
||||||
|
if (a.start.edge > b.start.edge)
|
||||||
|
return false;
|
||||||
|
if (a.end.clock.str(ctx) < b.end.clock.str(ctx))
|
||||||
|
return true;
|
||||||
|
if (a.end.clock.str(ctx) > b.end.clock.str(ctx))
|
||||||
|
return false;
|
||||||
|
if (a.end.edge < b.end.edge)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (print_path) {
|
if (print_path) {
|
||||||
if (crit_path.empty()) {
|
auto print_path_report = [ctx](ClockPair &clocks, PortRefVector &crit_path) {
|
||||||
log_info("Design contains no timing paths\n");
|
|
||||||
} else {
|
|
||||||
delay_t total = 0;
|
delay_t total = 0;
|
||||||
log_break();
|
|
||||||
log_info("Critical path report:\n");
|
|
||||||
log_info("curr total\n");
|
|
||||||
|
|
||||||
auto &front = crit_path.front();
|
auto &front = crit_path.front();
|
||||||
auto &front_port = front->cell->ports.at(front->port);
|
auto &front_port = front->cell->ports.at(front->port);
|
||||||
auto &front_driver = front_port.net->driver;
|
auto &front_driver = front_port.net->driver;
|
||||||
|
|
||||||
IdString last_port;
|
int port_clocks;
|
||||||
ctx->getPortTimingClass(front_driver.cell, front_driver.port, last_port);
|
auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks);
|
||||||
|
IdString last_port = front_driver.port;
|
||||||
|
if (portClass == TMG_REGISTER_OUTPUT) {
|
||||||
|
for (int i = 0; i < port_clocks; i++) {
|
||||||
|
TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i);
|
||||||
|
const NetInfo *clknet = get_net_or_empty(front_driver.cell, clockInfo.clock_port);
|
||||||
|
if (clknet != nullptr && clknet->name == clocks.start.clock &&
|
||||||
|
clockInfo.edge == clocks.start.edge) {
|
||||||
|
last_port = clockInfo.clock_port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("curr total\n");
|
||||||
for (auto sink : crit_path) {
|
for (auto sink : crit_path) {
|
||||||
auto sink_cell = sink->cell;
|
auto sink_cell = sink->cell;
|
||||||
auto &port = sink_cell->ports.at(sink->port);
|
auto &port = sink_cell->ports.at(sink->port);
|
||||||
@ -396,7 +618,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
|
|||||||
auto &driver = net->driver;
|
auto &driver = net->driver;
|
||||||
auto driver_cell = driver.cell;
|
auto driver_cell = driver.cell;
|
||||||
DelayInfo comb_delay;
|
DelayInfo comb_delay;
|
||||||
|
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);
|
ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
|
||||||
|
}
|
||||||
total += comb_delay.maxDelay();
|
total += comb_delay.maxDelay();
|
||||||
log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total),
|
log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total),
|
||||||
driver_cell->name.c_str(ctx), driver.port.c_str(ctx));
|
driver_cell->name.c_str(ctx), driver.port.c_str(ctx));
|
||||||
@ -427,12 +654,63 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
|
|||||||
}
|
}
|
||||||
last_port = sink->port;
|
last_port = sink->port;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto &clock : clock_reports) {
|
||||||
log_break();
|
log_break();
|
||||||
}
|
std::string start = clock.second.first.start.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge");
|
||||||
|
std::string end = clock.second.first.end.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge");
|
||||||
|
log_info("Critical path report for clock '%s' (%s -> %s):\n", clock.first.c_str(ctx), start.c_str(), end.c_str());
|
||||||
|
auto &crit_path = clock.second.second.ports;
|
||||||
|
print_path_report(clock.second.first, crit_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
delay_t default_slack = delay_t((1.0e9 / ctx->getDelayNS(1)) / ctx->target_freq);
|
for (auto &xclock : xclock_paths) {
|
||||||
log_info("estimated Fmax = %.2f MHz\n", 1e3 / ctx->getDelayNS(default_slack - min_slack));
|
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) {
|
if (print_histogram && slack_histogram.size() > 0) {
|
||||||
unsigned num_bins = 20;
|
unsigned num_bins = 20;
|
||||||
|
@ -29,7 +29,7 @@ void assign_budget(Context *ctx, bool quiet = false);
|
|||||||
|
|
||||||
// Perform timing analysis and print out the fmax, and optionally the
|
// Perform timing analysis and print out the fmax, and optionally the
|
||||||
// critical path
|
// critical path
|
||||||
void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_path = false);
|
void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false);
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
|
@ -404,6 +404,10 @@ actual penalty used is a multiple of this value (i.e. a weighted version of this
|
|||||||
|
|
||||||
Convert an `delay_t` to an actual real-world delay in nanoseconds.
|
Convert an `delay_t` to an actual real-world delay in nanoseconds.
|
||||||
|
|
||||||
|
### DelayInfo getDelayFromNS(float v) const
|
||||||
|
|
||||||
|
Convert a real-world delay in nanoseconds to a DelayInfo with equal min/max rising/falling values.
|
||||||
|
|
||||||
### uint32\_t getDelayChecksum(delay\_t v) const
|
### uint32\_t getDelayChecksum(delay\_t v) const
|
||||||
|
|
||||||
Convert a `delay_t` to an integer for checksum calculations.
|
Convert a `delay_t` to an integer for checksum calculations.
|
||||||
@ -461,11 +465,17 @@ Cell Delay Methods
|
|||||||
Returns the delay for the specified path through a cell in the `&delay` argument. The method returns
|
Returns the delay for the specified path through a cell in the `&delay` argument. The method returns
|
||||||
false if there is no timing relationship from `fromPort` to `toPort`.
|
false if there is no timing relationship from `fromPort` to `toPort`.
|
||||||
|
|
||||||
### TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const
|
### TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
|
||||||
|
|
||||||
Return the _timing port class_ of a port. This can be a register or combinational input or output; clock input or
|
Return the _timing port class_ of a port. This can be a register or combinational input or output; clock input or
|
||||||
output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockPort is set
|
output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockInfoCount is set
|
||||||
to the associated clock port.
|
to the number of associated _clock edges_ that can be queried by getPortClockingInfo.
|
||||||
|
|
||||||
|
### TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
|
||||||
|
|
||||||
|
Return the _clocking info_ (including port name of clock, clock polarity and setup/hold/clock-to-out times) of a
|
||||||
|
port. Where ports have more than one clock edge associated with them (such as DDR outputs), `index` can be used to obtain
|
||||||
|
information for all edges. `index` must be in [0, clockInfoCount), behaviour is undefined otherwise.
|
||||||
|
|
||||||
Placer Methods
|
Placer Methods
|
||||||
--------------
|
--------------
|
||||||
|
37
docs/constraints.md
Normal file
37
docs/constraints.md
Normal 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)
|
||||||
|
|
70
ecp5/arch.cc
70
ecp5/arch.cc
@ -576,10 +576,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const
|
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
|
||||||
{
|
{
|
||||||
auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; };
|
auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; };
|
||||||
|
clockInfoCount = 0;
|
||||||
if (cell->type == id_TRELLIS_SLICE) {
|
if (cell->type == id_TRELLIS_SLICE) {
|
||||||
int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0);
|
int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0);
|
||||||
if (port == id_CLK || port == id_WCK)
|
if (port == id_CLK || port == id_WCK)
|
||||||
@ -598,13 +598,13 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
|||||||
return TMG_COMB_OUTPUT;
|
return TMG_COMB_OUTPUT;
|
||||||
if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
|
if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
|
||||||
(sd1 == 1 && port == id_M1)) {
|
(sd1 == 1 && port == id_M1)) {
|
||||||
clockPort = id_CLK;
|
clockInfoCount = 1;
|
||||||
return TMG_REGISTER_INPUT;
|
return TMG_REGISTER_INPUT;
|
||||||
}
|
}
|
||||||
if (port == id_M0 || port == id_M1)
|
if (port == id_M0 || port == id_M1)
|
||||||
return TMG_COMB_INPUT;
|
return TMG_COMB_INPUT;
|
||||||
if (port == id_Q0 || port == id_Q1) {
|
if (port == id_Q0 || port == id_Q1) {
|
||||||
clockPort = id_CLK;
|
clockInfoCount = 1;
|
||||||
return TMG_REGISTER_OUTPUT;
|
return TMG_REGISTER_OUTPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,7 +614,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
|||||||
|
|
||||||
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
|
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
|
||||||
port == id_WAD3 || port == id_WRE) {
|
port == id_WAD3 || port == id_WRE) {
|
||||||
clockPort = id_WCK;
|
clockInfoCount = 1;
|
||||||
return TMG_REGISTER_INPUT;
|
return TMG_REGISTER_INPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -638,10 +638,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
|||||||
for (auto c : boost::adaptors::reverse(port_name)) {
|
for (auto c : boost::adaptors::reverse(port_name)) {
|
||||||
if (std::isdigit(c))
|
if (std::isdigit(c))
|
||||||
continue;
|
continue;
|
||||||
if (c == 'A')
|
if (c == 'A' || c == 'B')
|
||||||
clockPort = id_CLKA;
|
clockInfoCount = 1;
|
||||||
else if (c == 'B')
|
|
||||||
clockPort = id_CLKB;
|
|
||||||
else
|
else
|
||||||
NPNR_ASSERT_FALSE_STR("bad ram port");
|
NPNR_ASSERT_FALSE_STR("bad ram port");
|
||||||
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
|
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
|
||||||
@ -658,6 +656,60 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
|
||||||
|
{
|
||||||
|
TimingClockingInfo info;
|
||||||
|
info.setup.delay = 0;
|
||||||
|
info.hold.delay = 0;
|
||||||
|
info.clockToQ.delay = 0;
|
||||||
|
if (cell->type == id_TRELLIS_SLICE) {
|
||||||
|
int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0);
|
||||||
|
|
||||||
|
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
|
||||||
|
port == id_WAD3 || port == id_WRE) {
|
||||||
|
info.edge = RISING_EDGE;
|
||||||
|
info.clock_port = id_WCK;
|
||||||
|
info.setup.delay = 100;
|
||||||
|
info.hold.delay = 0;
|
||||||
|
} else if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
|
||||||
|
(sd1 == 1 && port == id_M1)) {
|
||||||
|
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
|
||||||
|
info.clock_port = id_CLK;
|
||||||
|
info.setup.delay = 100;
|
||||||
|
info.hold.delay = 0;
|
||||||
|
} else {
|
||||||
|
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
|
||||||
|
info.clock_port = id_CLK;
|
||||||
|
info.clockToQ.delay = 395;
|
||||||
|
}
|
||||||
|
} else if (cell->type == id_DP16KD) {
|
||||||
|
std::string port_name = port.str(this);
|
||||||
|
for (auto c : boost::adaptors::reverse(port_name)) {
|
||||||
|
if (std::isdigit(c))
|
||||||
|
continue;
|
||||||
|
if (c == 'A') {
|
||||||
|
info.clock_port = id_CLKA;
|
||||||
|
break;
|
||||||
|
} else if (c == 'B') {
|
||||||
|
info.clock_port = id_CLKB;
|
||||||
|
break;
|
||||||
|
} else
|
||||||
|
NPNR_ASSERT_FALSE_STR("bad ram port " + port.str(this));
|
||||||
|
}
|
||||||
|
info.edge = (str_or_default(cell->params, info.clock_port == id_CLKB ? id("CLKBMUX") : id("CLKAMUX"), "CLK") ==
|
||||||
|
"INV")
|
||||||
|
? FALLING_EDGE
|
||||||
|
: RISING_EDGE;
|
||||||
|
if (cell->ports.at(port).type == PORT_OUT) {
|
||||||
|
info.clockToQ.delay = 4280;
|
||||||
|
} else {
|
||||||
|
info.setup.delay = 100;
|
||||||
|
info.hold.delay = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::pair<std::string, std::string>> Arch::getTilesAtLocation(int row, int col)
|
std::vector<std::pair<std::string, std::string>> Arch::getTilesAtLocation(int row, int col)
|
||||||
{
|
{
|
||||||
std::vector<std::pair<std::string, std::string>> ret;
|
std::vector<std::pair<std::string, std::string>> ret;
|
||||||
|
12
ecp5/arch.h
12
ecp5/arch.h
@ -859,6 +859,12 @@ struct Arch : BaseCtx
|
|||||||
delay_t getDelayEpsilon() const { return 20; }
|
delay_t getDelayEpsilon() const { return 20; }
|
||||||
delay_t getRipupDelayPenalty() const { return 200; }
|
delay_t getRipupDelayPenalty() const { return 200; }
|
||||||
float getDelayNS(delay_t v) const { return v * 0.001; }
|
float getDelayNS(delay_t v) const { return v * 0.001; }
|
||||||
|
DelayInfo getDelayFromNS(float ns) const
|
||||||
|
{
|
||||||
|
DelayInfo del;
|
||||||
|
del.delay = delay_t(ns * 1000);
|
||||||
|
return del;
|
||||||
|
}
|
||||||
uint32_t getDelayChecksum(delay_t v) const { return v; }
|
uint32_t getDelayChecksum(delay_t v) const { return v; }
|
||||||
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
|
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
|
||||||
|
|
||||||
@ -882,8 +888,10 @@ struct Arch : BaseCtx
|
|||||||
// Get the delay through a cell from one port to another, returning false
|
// Get the delay through a cell from one port to another, returning false
|
||||||
// if no path exists
|
// if no path exists
|
||||||
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
|
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
|
||||||
// Get the port class, also setting clockPort if applicable
|
// Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
|
||||||
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const;
|
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
|
||||||
|
// Get the TimingClockingInfo of a port
|
||||||
|
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
|
||||||
// Return true if a port is a net
|
// Return true if a port is a net
|
||||||
bool isGlobalNet(const NetInfo *net) const;
|
bool isGlobalNet(const NetInfo *net) const;
|
||||||
|
|
||||||
|
@ -130,6 +130,10 @@ void arch_wrap_python()
|
|||||||
"cells");
|
"cells");
|
||||||
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
|
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
|
||||||
"nets");
|
"nets");
|
||||||
|
|
||||||
|
fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
|
||||||
|
pass_through<float>>::def_wrap(ctx_cls, "addClock");
|
||||||
|
|
||||||
WRAP_RANGE(Bel, conv_to_str<BelId>);
|
WRAP_RANGE(Bel, conv_to_str<BelId>);
|
||||||
WRAP_RANGE(Wire, conv_to_str<WireId>);
|
WRAP_RANGE(Wire, conv_to_str<WireId>);
|
||||||
WRAP_RANGE(AllPip, conv_to_str<PipId>);
|
WRAP_RANGE(AllPip, conv_to_str<PipId>);
|
||||||
|
@ -350,6 +350,13 @@ class Ecp5GlobalRouter
|
|||||||
|
|
||||||
place_dcc(dcc.get());
|
place_dcc(dcc.get());
|
||||||
|
|
||||||
|
if (net->clkconstr) {
|
||||||
|
glbnet->clkconstr = std::unique_ptr<ClockConstraint>(new ClockConstraint());
|
||||||
|
glbnet->clkconstr->low = net->clkconstr->low;
|
||||||
|
glbnet->clkconstr->high = net->clkconstr->high;
|
||||||
|
glbnet->clkconstr->period = net->clkconstr->period;
|
||||||
|
}
|
||||||
|
|
||||||
ctx->cells[dcc->name] = std::move(dcc);
|
ctx->cells[dcc->name] = std::move(dcc);
|
||||||
NetInfo *glbptr = glbnet.get();
|
NetInfo *glbptr = glbnet.get();
|
||||||
ctx->nets[glbnet->name] = std::move(glbnet);
|
ctx->nets[glbnet->name] = std::move(glbnet);
|
||||||
|
@ -463,11 +463,16 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the port class, also setting clockPort if applicable
|
// Get the port class, also setting clockPort if applicable
|
||||||
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const
|
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
|
||||||
{
|
{
|
||||||
return TMG_IGNORE;
|
return TMG_IGNORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
|
||||||
|
{
|
||||||
|
NPNR_ASSERT_FALSE("no clocking info for generic");
|
||||||
|
}
|
||||||
|
|
||||||
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; }
|
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; }
|
||||||
bool Arch::isBelLocationValid(BelId bel) const { return true; }
|
bool Arch::isBelLocationValid(BelId bel) const { return true; }
|
||||||
|
|
||||||
|
@ -211,6 +211,14 @@ struct Arch : BaseCtx
|
|||||||
delay_t getDelayEpsilon() const { return 0.01; }
|
delay_t getDelayEpsilon() const { return 0.01; }
|
||||||
delay_t getRipupDelayPenalty() const { return 1.0; }
|
delay_t getRipupDelayPenalty() const { return 1.0; }
|
||||||
float getDelayNS(delay_t v) const { return v; }
|
float getDelayNS(delay_t v) const { return v; }
|
||||||
|
|
||||||
|
DelayInfo getDelayFromNS(float ns) const
|
||||||
|
{
|
||||||
|
DelayInfo del;
|
||||||
|
del.delay = ns;
|
||||||
|
return del;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t getDelayChecksum(delay_t v) const { return 0; }
|
uint32_t getDelayChecksum(delay_t v) const { return 0; }
|
||||||
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
|
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
|
||||||
|
|
||||||
@ -225,8 +233,10 @@ struct Arch : BaseCtx
|
|||||||
DecalXY getGroupDecal(GroupId group) const;
|
DecalXY getGroupDecal(GroupId group) const;
|
||||||
|
|
||||||
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
|
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
|
||||||
// Get the port class, also setting clockPort if applicable
|
// Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
|
||||||
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const;
|
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
|
||||||
|
// Get the TimingClockingInfo of a port
|
||||||
|
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
|
||||||
|
|
||||||
bool isValidBelForCell(CellInfo *cell, BelId bel) const;
|
bool isValidBelForCell(CellInfo *cell, BelId bel) const;
|
||||||
bool isBelLocationValid(BelId bel) const;
|
bool isBelLocationValid(BelId bel) const;
|
||||||
|
@ -856,8 +856,9 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the port class, also setting clockPort to associated clock if applicable
|
// Get the port class, also setting clockPort to associated clock if applicable
|
||||||
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const
|
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
|
||||||
{
|
{
|
||||||
|
clockInfoCount = 0;
|
||||||
if (cell->type == id_ICESTORM_LC) {
|
if (cell->type == id_ICESTORM_LC) {
|
||||||
if (port == id_CLK)
|
if (port == id_CLK)
|
||||||
return TMG_CLOCK_INPUT;
|
return TMG_CLOCK_INPUT;
|
||||||
@ -870,18 +871,15 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
|||||||
if (cell->lcInfo.inputCount == 0)
|
if (cell->lcInfo.inputCount == 0)
|
||||||
return TMG_IGNORE;
|
return TMG_IGNORE;
|
||||||
if (cell->lcInfo.dffEnable) {
|
if (cell->lcInfo.dffEnable) {
|
||||||
clockPort = id_CLK;
|
clockInfoCount = 1;
|
||||||
return TMG_REGISTER_OUTPUT;
|
return TMG_REGISTER_OUTPUT;
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
return TMG_COMB_OUTPUT;
|
return TMG_COMB_OUTPUT;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (cell->lcInfo.dffEnable) {
|
if (cell->lcInfo.dffEnable) {
|
||||||
clockPort = id_CLK;
|
clockInfoCount = 1;
|
||||||
return TMG_REGISTER_INPUT;
|
return TMG_REGISTER_INPUT;
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
return TMG_COMB_INPUT;
|
return TMG_COMB_INPUT;
|
||||||
}
|
}
|
||||||
} else if (cell->type == id_ICESTORM_RAM) {
|
} else if (cell->type == id_ICESTORM_RAM) {
|
||||||
@ -889,23 +887,22 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
|||||||
if (port == id_RCLK || port == id_WCLK)
|
if (port == id_RCLK || port == id_WCLK)
|
||||||
return TMG_CLOCK_INPUT;
|
return TMG_CLOCK_INPUT;
|
||||||
|
|
||||||
if (port.str(this)[0] == 'R')
|
clockInfoCount = 1;
|
||||||
clockPort = id_RCLK;
|
|
||||||
else
|
|
||||||
clockPort = id_WCLK;
|
|
||||||
|
|
||||||
if (cell->ports.at(port).type == PORT_OUT)
|
if (cell->ports.at(port).type == PORT_OUT)
|
||||||
return TMG_REGISTER_OUTPUT;
|
return TMG_REGISTER_OUTPUT;
|
||||||
else
|
else
|
||||||
return TMG_REGISTER_INPUT;
|
return TMG_REGISTER_INPUT;
|
||||||
} else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) {
|
} else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) {
|
||||||
clockPort = id_CLK;
|
if (port == id_CLK || port == id_CLOCK)
|
||||||
if (port == id_CLK)
|
|
||||||
return TMG_CLOCK_INPUT;
|
return TMG_CLOCK_INPUT;
|
||||||
else if (cell->ports.at(port).type == PORT_OUT)
|
else {
|
||||||
|
clockInfoCount = 1;
|
||||||
|
if (cell->ports.at(port).type == PORT_OUT)
|
||||||
return TMG_REGISTER_OUTPUT;
|
return TMG_REGISTER_OUTPUT;
|
||||||
else
|
else
|
||||||
return TMG_REGISTER_INPUT;
|
return TMG_REGISTER_INPUT;
|
||||||
|
}
|
||||||
} else if (cell->type == id_SB_IO) {
|
} else if (cell->type == id_SB_IO) {
|
||||||
if (port == id_D_IN_0 || port == id_D_IN_1)
|
if (port == id_D_IN_0 || port == id_D_IN_1)
|
||||||
return TMG_STARTPOINT;
|
return TMG_STARTPOINT;
|
||||||
@ -934,6 +931,51 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
|||||||
log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this));
|
log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
|
||||||
|
{
|
||||||
|
TimingClockingInfo info;
|
||||||
|
if (cell->type == id_ICESTORM_LC) {
|
||||||
|
info.clock_port = id_CLK;
|
||||||
|
info.edge = cell->lcInfo.negClk ? FALLING_EDGE : RISING_EDGE;
|
||||||
|
if (port == id_O) {
|
||||||
|
bool has_clktoq = getCellDelay(cell, id_CLK, id_O, info.clockToQ);
|
||||||
|
NPNR_ASSERT(has_clktoq);
|
||||||
|
} else {
|
||||||
|
info.setup.delay = 100;
|
||||||
|
info.hold.delay = 0;
|
||||||
|
}
|
||||||
|
} else if (cell->type == id_ICESTORM_RAM) {
|
||||||
|
if (port.str(this)[0] == 'R') {
|
||||||
|
info.clock_port = id_RCLK;
|
||||||
|
info.edge = bool_or_default(cell->params, id("NEG_CLK_R")) ? FALLING_EDGE : RISING_EDGE;
|
||||||
|
} else {
|
||||||
|
info.clock_port = id_WCLK;
|
||||||
|
info.edge = bool_or_default(cell->params, id("NEG_CLK_W")) ? FALLING_EDGE : RISING_EDGE;
|
||||||
|
}
|
||||||
|
if (cell->ports.at(port).type == PORT_OUT) {
|
||||||
|
bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ);
|
||||||
|
NPNR_ASSERT(has_clktoq);
|
||||||
|
} else {
|
||||||
|
info.setup.delay = 100;
|
||||||
|
info.hold.delay = 0;
|
||||||
|
}
|
||||||
|
} else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) {
|
||||||
|
info.clock_port = cell->type == id_ICESTORM_SPRAM ? id_CLOCK : id_CLK;
|
||||||
|
info.edge = RISING_EDGE;
|
||||||
|
if (cell->ports.at(port).type == PORT_OUT) {
|
||||||
|
bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ);
|
||||||
|
if (!has_clktoq)
|
||||||
|
info.clockToQ.delay = 100;
|
||||||
|
} else {
|
||||||
|
info.setup.delay = 100;
|
||||||
|
info.hold.delay = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NPNR_ASSERT_FALSE("unhandled cell type in getPortClockingInfo");
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
bool Arch::isGlobalNet(const NetInfo *net) const
|
bool Arch::isGlobalNet(const NetInfo *net) const
|
||||||
{
|
{
|
||||||
if (net == nullptr)
|
if (net == nullptr)
|
||||||
|
12
ice40/arch.h
12
ice40/arch.h
@ -796,6 +796,12 @@ struct Arch : BaseCtx
|
|||||||
delay_t getDelayEpsilon() const { return 20; }
|
delay_t getDelayEpsilon() const { return 20; }
|
||||||
delay_t getRipupDelayPenalty() const { return 200; }
|
delay_t getRipupDelayPenalty() const { return 200; }
|
||||||
float getDelayNS(delay_t v) const { return v * 0.001; }
|
float getDelayNS(delay_t v) const { return v * 0.001; }
|
||||||
|
DelayInfo getDelayFromNS(float ns) const
|
||||||
|
{
|
||||||
|
DelayInfo del;
|
||||||
|
del.delay = delay_t(ns * 1000);
|
||||||
|
return del;
|
||||||
|
}
|
||||||
uint32_t getDelayChecksum(delay_t v) const { return v; }
|
uint32_t getDelayChecksum(delay_t v) const { return v; }
|
||||||
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
|
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
|
||||||
|
|
||||||
@ -819,8 +825,10 @@ struct Arch : BaseCtx
|
|||||||
// Get the delay through a cell from one port to another, returning false
|
// Get the delay through a cell from one port to another, returning false
|
||||||
// if no path exists
|
// if no path exists
|
||||||
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
|
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
|
||||||
// Get the port class, also setting clockDomain if applicable
|
// Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
|
||||||
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockDomain) const;
|
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
|
||||||
|
// Get the TimingClockingInfo of a port
|
||||||
|
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
|
||||||
// Return true if a port is a net
|
// Return true if a port is a net
|
||||||
bool isGlobalNet(const NetInfo *net) const;
|
bool isGlobalNet(const NetInfo *net) const;
|
||||||
|
|
||||||
|
@ -140,6 +140,10 @@ void arch_wrap_python()
|
|||||||
"cells");
|
"cells");
|
||||||
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
|
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
|
||||||
"nets");
|
"nets");
|
||||||
|
|
||||||
|
fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
|
||||||
|
pass_through<float>>::def_wrap(ctx_cls, "addClock");
|
||||||
|
|
||||||
WRAP_RANGE(Bel, conv_to_str<BelId>);
|
WRAP_RANGE(Bel, conv_to_str<BelId>);
|
||||||
WRAP_RANGE(Wire, conv_to_str<WireId>);
|
WRAP_RANGE(Wire, conv_to_str<WireId>);
|
||||||
WRAP_RANGE(AllPip, conv_to_str<PipId>);
|
WRAP_RANGE(AllPip, conv_to_str<PipId>);
|
||||||
|
@ -462,7 +462,8 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port)
|
|||||||
|
|
||||||
static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic)
|
static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic)
|
||||||
{
|
{
|
||||||
log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "", is_logic ? " [logic]" : "");
|
log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "",
|
||||||
|
is_logic ? " [logic]" : "");
|
||||||
|
|
||||||
std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk"));
|
std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk"));
|
||||||
std::unique_ptr<CellInfo> gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name);
|
std::unique_ptr<CellInfo> gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name);
|
||||||
@ -489,6 +490,14 @@ static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
net->users = keep_users;
|
net->users = keep_users;
|
||||||
|
|
||||||
|
if (net->clkconstr) {
|
||||||
|
glbnet->clkconstr = std::unique_ptr<ClockConstraint>(new ClockConstraint());
|
||||||
|
glbnet->clkconstr->low = net->clkconstr->low;
|
||||||
|
glbnet->clkconstr->high = net->clkconstr->high;
|
||||||
|
glbnet->clkconstr->period = net->clkconstr->period;
|
||||||
|
}
|
||||||
|
|
||||||
ctx->nets[glbnet->name] = std::move(glbnet);
|
ctx->nets[glbnet->name] = std::move(glbnet);
|
||||||
ctx->cells[gb->name] = std::move(gb);
|
ctx->cells[gb->name] = std::move(gb);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user