diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/nextpnr.cc | 137 | ||||
-rw-r--r-- | common/nextpnr.h | 135 | ||||
-rw-r--r-- | common/place_common.cc | 34 | ||||
-rw-r--r-- | common/placer1.cc | 38 | ||||
-rw-r--r-- | common/router1.cc | 1437 | ||||
-rw-r--r-- | common/router1.h | 4 | ||||
-rw-r--r-- | common/timing.cc | 568 | ||||
-rw-r--r-- | common/timing.h | 2 |
8 files changed, 1468 insertions, 887 deletions
diff --git a/common/nextpnr.cc b/common/nextpnr.cc index 4e6407b2..be3bfe14 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -18,6 +18,7 @@ */ #include "nextpnr.h" +#include "log.h" NEXTPNR_NAMESPACE_BEGIN @@ -25,6 +26,7 @@ assertion_failure::assertion_failure(std::string msg, std::string expr_str, std: : runtime_error("Assertion failure: " + msg + " (" + filename + ":" + std::to_string(line) + ")"), msg(msg), expr_str(expr_str), filename(filename), line(line) { + log_flush(); } void IdString::set(const BaseCtx *ctx, const std::string &s) @@ -51,6 +53,131 @@ 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(); + return ctx->getBelName(bel).c_str(ctx); +} + +const char *BaseCtx::nameOfWire(WireId wire) const +{ + const Context *ctx = getCtx(); + return ctx->getWireName(wire).c_str(ctx); +} + +const char *BaseCtx::nameOfPip(PipId pip) const +{ + const Context *ctx = getCtx(); + return ctx->getPipName(pip).c_str(ctx); +} + +const char *BaseCtx::nameOfGroup(GroupId group) const +{ + const Context *ctx = getCtx(); + return ctx->getGroupName(group).c_str(ctx); +} + WireId Context::getNetinfoSourceWire(const NetInfo *net_info) const { if (net_info->driver.cell == nullptr) @@ -280,4 +407,14 @@ void Context::check() const } } +void BaseCtx::addClock(IdString net, float freq) +{ + log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq); + std::unique_ptr<ClockConstraint> cc(new ClockConstraint()); + cc->period = getCtx()->getDelayFromNS(1000 / freq); + cc->high = getCtx()->getDelayFromNS(500 / freq); + cc->low = getCtx()->getDelayFromNS(500 / freq); + nets.at(net)->clkconstr = std::move(cc); +} + NEXTPNR_NAMESPACE_END diff --git a/common/nextpnr.h b/common/nextpnr.h index 59ae0323..a6617ae4 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -191,7 +191,15 @@ struct Loc Loc(int x, int y, int z) : x(x), y(y), z(z) {} 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); } + 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 @@ -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,10 +283,12 @@ struct PipMap PlaceStrength strength = STRENGTH_NONE; }; +struct ClockConstraint; + struct NetInfo : ArchNetInfo { IdString name; - int32_t udata; + int32_t udata = 0; PortRef driver; std::vector<PortRef> users; @@ -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() @@ -487,13 +579,23 @@ struct BaseCtx const Context *getCtx() const { return reinterpret_cast<const Context *>(this); } - template <typename T> const char *nameOf(const T *obj) + const char *nameOf(IdString name) const + { + return name.c_str(this); + } + + template <typename T> const char *nameOf(const T *obj) const { if (obj == nullptr) return ""; - return obj->name.c_str(getCtx()); + return obj->name.c_str(this); } + const char *nameOfBel(BelId bel) const; + const char *nameOfWire(WireId wire) const; + const char *nameOfPip(PipId pip) const; + const char *nameOfGroup(GroupId group) const; + // -------------------------------------------------------------- bool allUiReload = true; @@ -514,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 @@ -541,6 +667,7 @@ struct Context : Arch, DeterministicRNG delay_t getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &sink) const; // provided by router1.cc + bool checkRoutedDesign() const; bool getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t *delay = nullptr, std::unordered_map<WireId, PipId> *route = nullptr, bool useEstimate = true); diff --git a/common/place_common.cc b/common/place_common.cc index da8ab37d..a13a963c 100644 --- a/common/place_common.cc +++ b/common/place_common.cc @@ -28,19 +28,19 @@ NEXTPNR_NAMESPACE_BEGIN wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns) { wirelen_t wirelength = 0; - Loc driver_loc; - bool driver_gb; CellInfo *driver_cell = net->driver.cell; if (!driver_cell) return 0; if (driver_cell->bel == BelId()) return 0; - driver_gb = ctx->getBelGlobalBuf(driver_cell->bel); - driver_loc = ctx->getBelLocation(driver_cell->bel); + bool driver_gb = ctx->getBelGlobalBuf(driver_cell->bel); if (driver_gb) return 0; + 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); int xmin = driver_loc.x, xmax = driver_loc.x, ymin = driver_loc.y, ymax = driver_loc.y; for (auto load : net->users) { if (load.cell == nullptr) @@ -48,7 +48,7 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type CellInfo *load_cell = load.cell; if (load_cell->bel == BelId()) continue; - if (ctx->timing_driven && type == MetricType::COST) { + if (timing_driven) { delay_t net_delay = ctx->predictDelay(net, load); auto slack = load.budget - net_delay; if (slack < 0) @@ -65,7 +65,7 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type xmax = std::max(xmax, load_loc.x); ymax = std::max(ymax, load_loc.y); } - if (ctx->timing_driven && type == MetricType::COST) { + if (timing_driven) { wirelength = wirelen_t( (((ymax - ymin) + (xmax - xmin)) * std::min(5.0, (1.0 + std::exp(-ctx->getDelayNS(worst_slack) / 5))))); } else { @@ -431,12 +431,12 @@ class ConstraintLegaliseWorker print_chain(child, depth + 1); } - void print_stats(const char *point) + unsigned print_stats(const char *point) { float distance_sum = 0; float max_distance = 0; - int moved_cells = 0; - int unplaced_cells = 0; + unsigned moved_cells = 0; + unsigned unplaced_cells = 0; for (auto orig : oldLocations) { if (ctx->cells.at(orig.first)->bel == BelId()) { unplaced_cells++; @@ -456,9 +456,10 @@ class ConstraintLegaliseWorker log_info(" average distance %f\n", (distance_sum / moved_cells)); log_info(" maximum distance %f\n", max_distance); } + return moved_cells + unplaced_cells; } - bool legalise_constraints() + int legalise_constraints() { log_info("Legalising relative constraints...\n"); for (auto cell : sorted(ctx->cells)) { @@ -470,27 +471,28 @@ class ConstraintLegaliseWorker if (ctx->verbose) print_chain(cell.second); log_error("failed to place chain starting at cell '%s'\n", cell.first.c_str(ctx)); - return false; + return -1; } } - print_stats("after legalising chains"); + if (print_stats("legalising chains") == 0) + return 0; for (auto rippedCell : rippedCells) { bool res = place_single_cell(ctx, ctx->cells.at(rippedCell).get(), true); if (!res) { log_error("failed to place cell '%s' after relative constraint legalisation\n", rippedCell.c_str(ctx)); - return false; + return -1; } } - print_stats("after replacing ripped up cells"); + auto score = print_stats("replacing ripped up cells"); for (auto cell : sorted(ctx->cells)) if (get_constraints_distance(ctx, cell.second) != 0) log_error("constraint satisfaction check failed for cell '%s' at Bel '%s'\n", cell.first.c_str(ctx), ctx->getBelName(cell.second->bel).c_str(ctx)); - return true; + return score; } }; -bool legalise_relative_constraints(Context *ctx) { return ConstraintLegaliseWorker(ctx).legalise_constraints(); } +bool legalise_relative_constraints(Context *ctx) { return ConstraintLegaliseWorker(ctx).legalise_constraints() > 0; } // Get the total distance from satisfied constraints for a cell int get_constraints_distance(const Context *ctx, const CellInfo *cell) diff --git a/common/placer1.cc b/common/placer1.cc index 01f822a5..0fd9a227 100644 --- a/common/placer1.cc +++ b/common/placer1.cc @@ -118,6 +118,12 @@ class SAPlacer loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx)); } + auto bound_cell = ctx->getBoundBelCell(bel); + if (bound_cell) { + log_error("Cell \'%s\' cannot be bound to bel \'%s\' since it is already bound to cell \'%s\'\n", + cell->name.c_str(ctx), loc_name.c_str(), bound_cell->name.c_str(ctx)); + } + ctx->bindBel(bel, cell, STRENGTH_USER); locked_bels.insert(bel); placed_cells++; @@ -238,21 +244,23 @@ class SAPlacer } // Once cooled below legalise threshold, run legalisation and start requiring // legal moves only - if (temp < legalise_temp && !require_legal) { - legalise_relative_constraints(ctx); - require_legal = true; - autoplaced.clear(); - for (auto cell : sorted(ctx->cells)) { - if (cell.second->belStrength < STRENGTH_STRONG) - autoplaced.push_back(cell.second); - } - temp = post_legalise_temp; - diameter *= post_legalise_dia_scale; - ctx->shuffle(autoplaced); + if (temp < legalise_temp && require_legal) { + if (legalise_relative_constraints(ctx)) { + // Only increase temperature if something was moved + autoplaced.clear(); + for (auto cell : sorted(ctx->cells)) { + if (cell.second->belStrength < STRENGTH_STRONG) + autoplaced.push_back(cell.second); + } + temp = post_legalise_temp; + diameter *= post_legalise_dia_scale; + ctx->shuffle(autoplaced); - // Legalisation is a big change so force a slack redistribution here - if (ctx->slack_redist_iter > 0) - assign_budget(ctx, true /* quiet */); + // Legalisation is a big change so force a slack redistribution here + if (ctx->slack_redist_iter > 0) + assign_budget(ctx, true /* quiet */); + } + require_legal = false; } else if (ctx->slack_redist_iter > 0 && iter % ctx->slack_redist_iter == 0) { assign_budget(ctx, true /* quiet */); } @@ -480,7 +488,7 @@ class SAPlacer std::unordered_map<IdString, int> bel_types; std::vector<std::vector<std::vector<std::vector<BelId>>>> fast_bels; std::unordered_set<BelId> locked_bels; - bool require_legal = false; + bool require_legal = true; const float legalise_temp = 1; const float post_legalise_temp = 10; const float post_legalise_dia_scale = 1.5; diff --git a/common/router1.cc b/common/router1.cc index c4708de7..958c24d4 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -28,24 +28,40 @@ namespace { USING_NEXTPNR_NAMESPACE -struct hash_id_wire +struct arc_key { - std::size_t operator()(const std::pair<IdString, WireId> &arg) const noexcept + NetInfo *net_info; + int user_idx; + + bool operator==(const arc_key &other) const { return (net_info == other.net_info) && (user_idx == other.user_idx); } + bool operator<(const arc_key &other) const { return net_info == other.net_info ? user_idx < other.user_idx : net_info->name < other.net_info->name; } + + struct Hash { - std::size_t seed = std::hash<IdString>()(arg.first); - seed ^= std::hash<WireId>()(arg.second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - return seed; - } + std::size_t operator()(const arc_key &arg) const noexcept + { + std::size_t seed = std::hash<NetInfo *>()(arg.net_info); + seed ^= std::hash<int>()(arg.user_idx) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } + }; }; -struct hash_id_pip +struct arc_entry { - std::size_t operator()(const std::pair<IdString, PipId> &arg) const noexcept + arc_key arc; + delay_t pri; + int randtag = 0; + + struct Less { - std::size_t seed = std::hash<IdString>()(arg.first); - seed ^= std::hash<PipId>()(arg.second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - return seed; - } + bool operator()(const arc_entry &lhs, const arc_entry &rhs) const noexcept + { + if (lhs.pri != rhs.pri) + return lhs.pri < rhs.pri; + return lhs.randtag < rhs.randtag; + } + }; }; struct QueuedWire @@ -53,636 +69,662 @@ struct QueuedWire WireId wire; PipId pip; - delay_t delay = 0, togo = 0; + delay_t delay = 0, penalty = 0, bonus = 0, togo = 0; int randtag = 0; struct Greater { bool operator()(const QueuedWire &lhs, const QueuedWire &rhs) const noexcept { - delay_t l = lhs.delay + lhs.togo, r = rhs.delay + rhs.togo; + delay_t l = lhs.delay + lhs.penalty + lhs.togo; + delay_t r = rhs.delay + rhs.penalty + rhs.togo; + NPNR_ASSERT(l >= 0); + NPNR_ASSERT(r >= 0); + l -= lhs.bonus; + r -= rhs.bonus; return l == r ? lhs.randtag > rhs.randtag : l > r; } }; }; -struct RipupScoreboard +struct Router1 { - std::unordered_map<WireId, int> wireScores; - std::unordered_map<PipId, int> pipScores; - std::unordered_map<std::pair<IdString, WireId>, int, hash_id_wire> netWireScores; - std::unordered_map<std::pair<IdString, PipId>, int, hash_id_pip> netPipScores; -}; + Context *ctx; + const Router1Cfg &cfg; -void ripup_net(Context *ctx, IdString net_name) -{ - if (ctx->debug) - log("Ripping up all routing for net %s.\n", net_name.c_str(ctx)); + std::priority_queue<arc_entry, std::vector<arc_entry>, arc_entry::Less> arc_queue; + std::unordered_map<WireId, std::unordered_set<arc_key, arc_key::Hash>> wire_to_arcs; + std::unordered_map<arc_key, std::unordered_set<WireId>, arc_key::Hash> arc_to_wires; + std::unordered_set<arc_key, arc_key::Hash> queued_arcs; - auto net_info = ctx->nets.at(net_name).get(); - std::vector<PipId> pips; - std::vector<WireId> wires; + std::unordered_map<WireId, QueuedWire> visited; + std::priority_queue<QueuedWire, std::vector<QueuedWire>, QueuedWire::Greater> queue; - pips.reserve(net_info->wires.size()); - wires.reserve(net_info->wires.size()); + std::unordered_map<WireId, int> wireScores; + std::unordered_map<NetInfo *, int> netScores; - for (auto &it : net_info->wires) { - if (it.second.pip != PipId()) - pips.push_back(it.second.pip); - else - wires.push_back(it.first); - } + int arcs_with_ripup = 0; + int arcs_without_ripup = 0; + bool ripup_flag; - for (auto pip : pips) - ctx->unbindPip(pip); + Router1(Context *ctx, const Router1Cfg &cfg) : ctx(ctx), cfg(cfg) {} - for (auto wire : wires) - ctx->unbindWire(wire); + void arc_queue_insert(const arc_key &arc, WireId src_wire, WireId dst_wire) + { + if (queued_arcs.count(arc)) + return; - NPNR_ASSERT(net_info->wires.empty()); -} + delay_t pri = ctx->estimateDelay(src_wire, dst_wire) - arc.net_info->users[arc.user_idx].budget; -struct Router -{ - Context *ctx; - const Router1Cfg &cfg; - RipupScoreboard scores; - IdString net_name; + arc_entry entry; + entry.arc = arc; + entry.pri = pri; + entry.randtag = ctx->rng(); - bool ripup; - delay_t ripup_penalty; +#if 0 + if (ctx->debug) + log("[arc_queue_insert] %s (%d) %s %s [%d %d]\n", ctx->nameOf(entry.arc.net_info), entry.arc.user_idx, + ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire), (int)entry.pri, entry.randtag); +#endif - std::unordered_set<IdString> rippedNets; - std::unordered_map<WireId, QueuedWire> visited; - int visitCnt = 0, revisitCnt = 0, overtimeRevisitCnt = 0; - bool routedOkay = false; - delay_t maxDelay = 0.0; - WireId failedDest; + arc_queue.push(entry); + queued_arcs.insert(arc); + } - void route(const std::unordered_map<WireId, delay_t> &src_wires, WireId dst_wire) + void arc_queue_insert(const arc_key &arc) { - std::priority_queue<QueuedWire, std::vector<QueuedWire>, QueuedWire::Greater> queue; + if (queued_arcs.count(arc)) + return; - visited.clear(); + NetInfo *net_info = arc.net_info; + int user_idx = arc.user_idx; - for (auto &it : src_wires) { - QueuedWire qw; - qw.wire = it.first; - qw.pip = PipId(); - qw.delay = it.second - (it.second / 16); - if (cfg.useEstimate) - qw.togo = ctx->estimateDelay(qw.wire, dst_wire); - qw.randtag = ctx->rng(); + auto src_wire = ctx->getNetinfoSourceWire(net_info); + auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); - queue.push(qw); - visited[qw.wire] = qw; - } + arc_queue_insert(arc, src_wire, dst_wire); + } - int thisVisitCnt = 0; - int thisVisitCntLimit = 0; + arc_key arc_queue_pop() + { + arc_entry entry = arc_queue.top(); - while (!queue.empty() && (thisVisitCntLimit == 0 || thisVisitCnt < thisVisitCntLimit)) { - QueuedWire qw = queue.top(); - queue.pop(); +#if 0 + if (ctx->debug) + log("[arc_queue_pop] %s (%d) [%d %d]\n", ctx->nameOf(entry.arc.net_info), entry.arc.user_idx, + (int)entry.pri, entry.randtag); +#endif - if (thisVisitCntLimit == 0 && visited.count(dst_wire)) - thisVisitCntLimit = (thisVisitCnt * 3) / 2; + arc_queue.pop(); + queued_arcs.erase(entry.arc); + return entry.arc; + } - for (auto pip : ctx->getPipsDownhill(qw.wire)) { - delay_t next_delay = qw.delay + ctx->getPipDelay(pip).maxDelay(); - WireId next_wire = ctx->getPipDstWire(pip); - bool foundRipupNet = false; - thisVisitCnt++; + void ripup_net(NetInfo *net) + { + if (ctx->debug) + log(" ripup net %s\n", ctx->nameOf(net)); - next_delay += ctx->getWireDelay(next_wire).maxDelay(); + netScores[net]++; - if (!ctx->checkWireAvail(next_wire)) { - if (!ripup) - continue; - NetInfo *ripupWireNet = ctx->getConflictingWireNet(next_wire); - if (ripupWireNet == nullptr || ripupWireNet->name == net_name) - continue; + std::vector<WireId> wires; + for (auto &it : net->wires) + wires.push_back(it.first); - auto it1 = scores.wireScores.find(next_wire); - if (it1 != scores.wireScores.end()) - next_delay += (it1->second * ripup_penalty) / 8; + ctx->sorted_shuffle(wires); - auto it2 = scores.netWireScores.find(std::make_pair(ripupWireNet->name, next_wire)); - if (it2 != scores.netWireScores.end()) - next_delay += it2->second * ripup_penalty; + for (WireId w : wires) { + std::vector<arc_key> arcs; + for (auto &it : wire_to_arcs[w]) { + arc_to_wires[it].erase(w); + arcs.push_back(it); + } + wire_to_arcs[w].clear(); - foundRipupNet = true; - } + ctx->sorted_shuffle(arcs); - if (!ctx->checkPipAvail(pip)) { - if (!ripup) - continue; - NetInfo *ripupPipNet = ctx->getConflictingPipNet(pip); - if (ripupPipNet == nullptr || ripupPipNet->name == net_name) - continue; + for (auto &it : arcs) + arc_queue_insert(it); - auto it1 = scores.pipScores.find(pip); - if (it1 != scores.pipScores.end()) - next_delay += (it1->second * ripup_penalty) / 8; + if (ctx->debug) + log(" unbind wire %s\n", ctx->nameOfWire(w)); - auto it2 = scores.netPipScores.find(std::make_pair(ripupPipNet->name, pip)); - if (it2 != scores.netPipScores.end()) - next_delay += it2->second * ripup_penalty; + ctx->unbindWire(w); + wireScores[w]++; + } - foundRipupNet = true; - } + ripup_flag = true; + } - if (foundRipupNet) - next_delay += ripup_penalty; + void ripup_wire(WireId wire, int extra_indent = 0) + { + if (ctx->debug) + log(" ripup wire %s\n", ctx->nameOfWire(wire)); - NPNR_ASSERT(next_delay >= 0); + WireId w = ctx->getConflictingWireWire(wire); - if (visited.count(next_wire)) { - if (visited.at(next_wire).delay <= next_delay + ctx->getDelayEpsilon()) - continue; -#if 0 // FIXME - if (ctx->debug) - log("Found better route to %s. Old vs new delay estimate: %.3f %.3f\n", - ctx->getWireName(next_wire).c_str(), - ctx->getDelayNS(visited.at(next_wire).delay), - ctx->getDelayNS(next_delay)); -#endif - if (thisVisitCntLimit == 0) - revisitCnt++; - else - overtimeRevisitCnt++; - } + if (w == WireId()) { + NetInfo *n = ctx->getConflictingWireNet(wire); + if (n != nullptr) + ripup_net(n); + } else { + std::vector<arc_key> arcs; + for (auto &it : wire_to_arcs[w]) { + arc_to_wires[it].erase(w); + arcs.push_back(it); + } + wire_to_arcs[w].clear(); - QueuedWire next_qw; - next_qw.wire = next_wire; - next_qw.pip = pip; - next_qw.delay = next_delay; - if (cfg.useEstimate) - next_qw.togo = ctx->estimateDelay(next_wire, dst_wire); - next_qw.randtag = ctx->rng(); + ctx->sorted_shuffle(arcs); - visited[next_qw.wire] = next_qw; - queue.push(next_qw); - } + for (auto &it : arcs) + arc_queue_insert(it); + + if (ctx->debug) + log(" unbind wire %s\n", ctx->nameOfWire(w)); + + ctx->unbindWire(w); + wireScores[w]++; } - visitCnt += thisVisitCnt; + ripup_flag = true; } - Router(Context *ctx, const Router1Cfg &cfg, RipupScoreboard &scores, WireId src_wire, WireId dst_wire, - bool ripup = false, delay_t ripup_penalty = 0) - : ctx(ctx), cfg(cfg), scores(scores), ripup(ripup), ripup_penalty(ripup_penalty) + void ripup_pip(PipId pip) { - std::unordered_map<WireId, delay_t> src_wires; - src_wires[src_wire] = ctx->getWireDelay(src_wire).maxDelay(); - route(src_wires, dst_wire); - routedOkay = visited.count(dst_wire); + if (ctx->debug) + log(" ripup pip %s\n", ctx->nameOfPip(pip)); - if (ctx->debug) { - log("Route (from destination to source):\n"); + WireId w = ctx->getConflictingPipWire(pip); + + if (w == WireId()) { + NetInfo *n = ctx->getConflictingPipNet(pip); + if (n != nullptr) + ripup_net(n); + } else { + std::vector<arc_key> arcs; + for (auto &it : wire_to_arcs[w]) { + arc_to_wires[it].erase(w); + arcs.push_back(it); + } + wire_to_arcs[w].clear(); - WireId cursor = dst_wire; + ctx->sorted_shuffle(arcs); - while (1) { - log(" %8.3f %s\n", ctx->getDelayNS(visited[cursor].delay), ctx->getWireName(cursor).c_str(ctx)); + for (auto &it : arcs) + arc_queue_insert(it); - if (cursor == src_wire) - break; + if (ctx->debug) + log(" unbind wire %s\n", ctx->nameOfWire(w)); - cursor = ctx->getPipSrcWire(visited[cursor].pip); - } + ctx->unbindWire(w); + wireScores[w]++; } + + ripup_flag = true; } - Router(Context *ctx, const Router1Cfg &cfg, RipupScoreboard &scores, IdString net_name, int user_idx = -1, - bool reroute = false, bool ripup = false, delay_t ripup_penalty = 0) - : ctx(ctx), cfg(cfg), scores(scores), net_name(net_name), ripup(ripup), ripup_penalty(ripup_penalty) + bool skip_net(NetInfo *net_info) { - auto net_info = ctx->nets.at(net_name).get(); - - if (ctx->debug) - log("Routing net %s.\n", net_name.c_str(ctx)); +#ifdef ARCH_ECP5 + // ECP5 global nets currently appear part-unrouted due to arch database limitations + // Don't touch them in the router + if (net_info->is_global) + return true; +#endif + if (net_info->driver.cell == nullptr) + return true; - if (ctx->debug) - log(" Source: %s.%s.\n", net_info->driver.cell->name.c_str(ctx), net_info->driver.port.c_str(ctx)); + return false; + } - auto src_wire = ctx->getNetinfoSourceWire(net_info); + void check() + { + std::unordered_set<arc_key, arc_key::Hash> valid_arcs; - if (src_wire == WireId()) - log_error("No wire found for port %s on source cell %s.\n", net_info->driver.port.c_str(ctx), - net_info->driver.cell->name.c_str(ctx)); + for (auto &net_it : ctx->nets) { + NetInfo *net_info = net_it.second.get(); + std::unordered_set<WireId> valid_wires_for_net; - if (ctx->debug) - log(" Source wire: %s\n", ctx->getWireName(src_wire).c_str(ctx)); - - std::unordered_map<WireId, delay_t> src_wires; - std::vector<std::pair<delay_t, int>> users_array; + if (skip_net(net_info)) + continue; - if (user_idx < 0) { - // route all users, from worst to best slack - for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); - delay_t slack = net_info->users[user_idx].budget - ctx->estimateDelay(src_wire, dst_wire); - users_array.push_back(std::pair<delay_t, int>(slack, user_idx)); - } - std::sort(users_array.begin(), users_array.end()); - } else { - // route only the selected user - users_array.push_back(std::pair<delay_t, int>(delay_t(), user_idx)); - } +#if 0 + if (ctx->debug) + log("[check] net: %s\n", ctx->nameOf(net_info)); +#endif - if (reroute) { - // complete ripup - ripup_net(ctx, net_name); - ctx->bindWire(src_wire, ctx->nets.at(net_name).get(), STRENGTH_WEAK); - src_wires[src_wire] = ctx->getWireDelay(src_wire).maxDelay(); - } else { - // re-use existing routes as much as possible - if (net_info->wires.count(src_wire) == 0) - ctx->bindWire(src_wire, ctx->nets.at(net_name).get(), STRENGTH_WEAK); - src_wires[src_wire] = ctx->getWireDelay(src_wire).maxDelay(); + auto src_wire = ctx->getNetinfoSourceWire(net_info); + log_assert(src_wire != WireId()); for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + log_assert(dst_wire != WireId()); - if (dst_wire == WireId()) - log_error("No wire found for port %s on destination cell %s.\n", - net_info->users[user_idx].port.c_str(ctx), - net_info->users[user_idx].cell->name.c_str(ctx)); - - std::function<delay_t(WireId)> register_existing_path = - [ctx, net_info, &src_wires, ®ister_existing_path](WireId wire) -> delay_t { - auto it = src_wires.find(wire); - if (it != src_wires.end()) - return it->second; - - PipId pip = net_info->wires.at(wire).pip; - delay_t delay = register_existing_path(ctx->getPipSrcWire(pip)); - delay += ctx->getPipDelay(pip).maxDelay(); - delay += ctx->getWireDelay(wire).maxDelay(); - src_wires[wire] = delay; + arc_key arc; + arc.net_info = net_info; + arc.user_idx = user_idx; - return delay; - }; + valid_arcs.insert(arc); +#if 0 + if (ctx->debug) + log("[check] arc: %s %s\n", ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire)); +#endif - WireId cursor = dst_wire; - while (src_wires.count(cursor) == 0) { - auto it = net_info->wires.find(cursor); - if (it == net_info->wires.end()) - goto check_next_user_for_existing_path; - NPNR_ASSERT(it->second.pip != PipId()); - cursor = ctx->getPipSrcWire(it->second.pip); + for (WireId wire : arc_to_wires[arc]) { +#if 0 + if (ctx->debug) + log("[check] wire: %s\n", ctx->nameOfWire(wire)); +#endif + valid_wires_for_net.insert(wire); + log_assert(wire_to_arcs[wire].count(arc)); + log_assert(net_info->wires.count(wire)); } - - register_existing_path(dst_wire); - check_next_user_for_existing_path:; } - std::vector<WireId> ripup_wires; - for (auto &it : net_info->wires) - if (src_wires.count(it.first) == 0) - ripup_wires.push_back(it.first); - - for (auto &it : ripup_wires) { - if (ctx->debug) - log(" Unbind dangling wire for net %s: %s\n", net_name.c_str(ctx), - ctx->getWireName(it).c_str(ctx)); - ctx->unbindWire(it); + for (auto &it : net_info->wires) { + WireId w = it.first; + log_assert(valid_wires_for_net.count(w)); } } - for (auto user_idx_it : users_array) { - int user_idx = user_idx_it.second; + for (auto &it : wire_to_arcs) { + for (auto &arc : it.second) + log_assert(valid_arcs.count(arc)); + } - if (ctx->debug) - log(" Route to: %s.%s.\n", net_info->users[user_idx].cell->name.c_str(ctx), - net_info->users[user_idx].port.c_str(ctx)); + for (auto &it : arc_to_wires) { + log_assert(valid_arcs.count(it.first)); + } + } - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + void setup() + { + std::unordered_map<WireId, NetInfo *> src_to_net; + std::unordered_map<WireId, arc_key> dst_to_arc; - if (dst_wire == WireId()) - log_error("No wire found for port %s on destination cell %s.\n", - net_info->users[user_idx].port.c_str(ctx), net_info->users[user_idx].cell->name.c_str(ctx)); + std::vector<IdString> net_names; + for (auto &net_it : ctx->nets) + net_names.push_back(net_it.first); - if (ctx->debug) { - log(" Destination wire: %s\n", ctx->getWireName(dst_wire).c_str(ctx)); - log(" Path delay estimate: %.2f\n", float(ctx->estimateDelay(src_wire, dst_wire))); - } + ctx->sorted_shuffle(net_names); - route(src_wires, dst_wire); + for (IdString net_name : net_names) { + NetInfo *net_info = ctx->nets.at(net_name).get(); - if (visited.count(dst_wire) == 0) { - if (ctx->debug) - log("Failed to route %s -> %s.\n", ctx->getWireName(src_wire).c_str(ctx), - ctx->getWireName(dst_wire).c_str(ctx)); - else if (ripup) - log_info("Failed to route %s -> %s.\n", ctx->getWireName(src_wire).c_str(ctx), - ctx->getWireName(dst_wire).c_str(ctx)); - ripup_net(ctx, net_name); - failedDest = dst_wire; - return; - } + if (skip_net(net_info)) + continue; - if (ctx->debug) - log(" Final path delay: %.3f\n", ctx->getDelayNS(visited[dst_wire].delay)); - maxDelay = fmaxf(maxDelay, visited[dst_wire].delay); + auto src_wire = ctx->getNetinfoSourceWire(net_info); - if (ctx->debug) - log(" Route (from destination to source):\n"); + if (src_wire == WireId()) + log_error("No wire found for port %s on source cell %s.\n", ctx->nameOf(net_info->driver.port), + ctx->nameOf(net_info->driver.cell)); - WireId cursor = dst_wire; + if (src_to_net.count(src_wire)) + log_error("Found two nets with same source wire %s: %s vs %s\n", ctx->nameOfWire(src_wire), + ctx->nameOf(net_info), ctx->nameOf(src_to_net.at(src_wire))); - while (1) { - if (ctx->debug) - log(" %8.3f %s\n", ctx->getDelayNS(visited[cursor].delay), ctx->getWireName(cursor).c_str(ctx)); + if (dst_to_arc.count(src_wire)) + log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n", + ctx->nameOfWire(src_wire), ctx->nameOf(net_info), + ctx->nameOf(dst_to_arc.at(src_wire).net_info), dst_to_arc.at(src_wire).user_idx); - if (src_wires.count(cursor)) - break; + for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { + auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); - NetInfo *conflicting_wire_net = ctx->getConflictingWireNet(cursor); + if (dst_wire == WireId()) + log_error("No wire found for port %s on destination cell %s.\n", + ctx->nameOf(net_info->users[user_idx].port), + ctx->nameOf(net_info->users[user_idx].cell)); - if (conflicting_wire_net != nullptr) { - NPNR_ASSERT(ripup); - NPNR_ASSERT(conflicting_wire_net->name != net_name); + if (dst_to_arc.count(dst_wire)) { + if (dst_to_arc.at(dst_wire).net_info == net_info) + continue; + log_error("Found two arcs with same sink wire %s: %s (%d) vs %s (%d)\n", + ctx->nameOfWire(dst_wire), ctx->nameOf(net_info), user_idx, + ctx->nameOf(dst_to_arc.at(dst_wire).net_info), dst_to_arc.at(dst_wire).user_idx); + } - ctx->unbindWire(cursor); - if (!ctx->checkWireAvail(cursor)) - ripup_net(ctx, conflicting_wire_net->name); + if (src_to_net.count(dst_wire)) + log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n", + ctx->nameOfWire(dst_wire), ctx->nameOf(src_to_net.at(dst_wire)), + ctx->nameOf(net_info), user_idx); - rippedNets.insert(conflicting_wire_net->name); - scores.wireScores[cursor]++; - scores.netWireScores[std::make_pair(net_name, cursor)]++; - scores.netWireScores[std::make_pair(conflicting_wire_net->name, cursor)]++; - } + arc_key arc; + arc.net_info = net_info; + arc.user_idx = user_idx; - PipId pip = visited[cursor].pip; - NetInfo *conflicting_pip_net = ctx->getConflictingPipNet(pip); + dst_to_arc[dst_wire] = arc; - if (conflicting_pip_net != nullptr) { - NPNR_ASSERT(ripup); - NPNR_ASSERT(conflicting_pip_net->name != net_name); + if (net_info->wires.count(src_wire) == 0) { + arc_queue_insert(arc, src_wire, dst_wire); + continue; + } - if (ctx->getBoundPipNet(pip) == conflicting_pip_net) - ctx->unbindPip(pip); + WireId cursor = dst_wire; + wire_to_arcs[cursor].insert(arc); + arc_to_wires[arc].insert(cursor); - if (!ctx->checkPipAvail(pip)) - ripup_net(ctx, conflicting_pip_net->name); + while (src_wire != cursor) { + auto it = net_info->wires.find(cursor); + if (it == net_info->wires.end()) { + arc_queue_insert(arc, src_wire, dst_wire); + break; + } - rippedNets.insert(conflicting_pip_net->name); - scores.pipScores[visited[cursor].pip]++; - scores.netPipScores[std::make_pair(net_name, visited[cursor].pip)]++; - scores.netPipScores[std::make_pair(conflicting_pip_net->name, visited[cursor].pip)]++; + NPNR_ASSERT(it->second.pip != PipId()); + cursor = ctx->getPipSrcWire(it->second.pip); + wire_to_arcs[cursor].insert(arc); + arc_to_wires[arc].insert(cursor); } - - ctx->bindPip(visited[cursor].pip, ctx->nets.at(net_name).get(), STRENGTH_WEAK); - src_wires[cursor] = visited[cursor].delay; - cursor = ctx->getPipSrcWire(visited[cursor].pip); } - } - routedOkay = true; - } -}; + src_to_net[src_wire] = net_info; -struct RouteJob -{ - IdString net; - int user_idx = -1; - delay_t slack = 0; - int randtag = 0; + std::vector<WireId> unbind_wires; - struct Greater - { - bool operator()(const RouteJob &lhs, const RouteJob &rhs) const noexcept - { - return lhs.slack == rhs.slack ? lhs.randtag > rhs.randtag : lhs.slack > rhs.slack; - } - }; -}; + for (auto &it : net_info->wires) + if (it.second.strength < STRENGTH_LOCKED && wire_to_arcs.count(it.first) == 0) + unbind_wires.push_back(it.first); -void addFullNetRouteJob(Context *ctx, const Router1Cfg &cfg, IdString net_name, - std::unordered_map<IdString, std::vector<bool>> &cache, - std::priority_queue<RouteJob, std::vector<RouteJob>, RouteJob::Greater> &queue) -{ - NetInfo *net_info = ctx->nets.at(net_name).get(); + for (auto it : unbind_wires) + ctx->unbindWire(it); + } + } - if (net_info->driver.cell == nullptr) - return; + bool route_arc(const arc_key &arc, bool ripup) + { - auto src_wire = ctx->getNetinfoSourceWire(net_info); + NetInfo *net_info = arc.net_info; + int user_idx = arc.user_idx; - if (src_wire == WireId()) - log_error("No wire found for port %s on source cell %s.\n", net_info->driver.port.c_str(ctx), - net_info->driver.cell->name.c_str(ctx)); + auto src_wire = ctx->getNetinfoSourceWire(net_info); + auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + ripup_flag = false; - auto &net_cache = cache[net_name]; + if (ctx->debug) { + log("Routing arc %d on net %s (%d arcs total):\n", user_idx, ctx->nameOf(net_info), + int(net_info->users.size())); + log(" source ... %s\n", ctx->nameOfWire(src_wire)); + log(" sink ..... %s\n", ctx->nameOfWire(dst_wire)); + } - if (net_cache.empty()) - net_cache.resize(net_info->users.size()); + // unbind wires that are currently used exclusively by this arc - RouteJob job; - job.net = net_name; - job.user_idx = -1; - job.slack = 0; - job.randtag = ctx->rng(); + std::unordered_set<WireId> old_arc_wires; + old_arc_wires.swap(arc_to_wires[arc]); - bool got_slack = false; + for (WireId wire : old_arc_wires) { + auto &arc_wires = wire_to_arcs.at(wire); + NPNR_ASSERT(arc_wires.count(arc)); + arc_wires.erase(arc); + if (arc_wires.empty()) { + if (ctx->debug) + log(" unbind %s\n", ctx->nameOfWire(wire)); + ctx->unbindWire(wire); + } + } - for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { - if (net_cache[user_idx]) - continue; + // reset wire queue - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + if (!queue.empty()) { + std::priority_queue<QueuedWire, std::vector<QueuedWire>, QueuedWire::Greater> new_queue; + queue.swap(new_queue); + } + visited.clear(); - if (dst_wire == WireId()) - log_error("No wire found for port %s on destination cell %s.\n", net_info->users[user_idx].port.c_str(ctx), - net_info->users[user_idx].cell->name.c_str(ctx)); + // A* main loop - if (user_idx == 0) - job.slack = net_info->users[user_idx].budget - ctx->estimateDelay(src_wire, dst_wire); - else - job.slack = std::min(job.slack, net_info->users[user_idx].budget - ctx->estimateDelay(src_wire, dst_wire)); + int visitCnt = 0; + int maxVisitCnt = INT_MAX; + delay_t best_est = 0; + delay_t best_score = -1; - WireId cursor = dst_wire; - while (src_wire != cursor) { - auto it = net_info->wires.find(cursor); - if (it == net_info->wires.end()) { - if (!got_slack) - job.slack = net_info->users[user_idx].budget - ctx->estimateDelay(src_wire, dst_wire); - else - job.slack = std::min(job.slack, - net_info->users[user_idx].budget - ctx->estimateDelay(src_wire, dst_wire)); - got_slack = true; - break; + { + QueuedWire qw; + qw.wire = src_wire; + qw.pip = PipId(); + qw.delay = ctx->getWireDelay(qw.wire).maxDelay(); + qw.penalty = 0; + qw.bonus = 0; + if (cfg.useEstimate) { + qw.togo = ctx->estimateDelay(qw.wire, dst_wire); + best_est = qw.delay + qw.togo; } - NPNR_ASSERT(it->second.pip != PipId()); - cursor = ctx->getPipSrcWire(it->second.pip); + qw.randtag = ctx->rng(); + + queue.push(qw); + visited[qw.wire] = qw; } - } - queue.push(job); + while (visitCnt++ < maxVisitCnt && !queue.empty()) { + QueuedWire qw = queue.top(); + queue.pop(); - for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) - net_cache[user_idx] = true; -} + for (auto pip : ctx->getPipsDownhill(qw.wire)) { + delay_t next_delay = qw.delay + ctx->getPipDelay(pip).maxDelay(); + delay_t next_penalty = qw.penalty; + delay_t next_bonus = qw.bonus; -void addNetRouteJobs(Context *ctx, const Router1Cfg &cfg, IdString net_name, - std::unordered_map<IdString, std::vector<bool>> &cache, - std::priority_queue<RouteJob, std::vector<RouteJob>, RouteJob::Greater> &queue) -{ - NetInfo *net_info = ctx->nets.at(net_name).get(); + WireId next_wire = ctx->getPipDstWire(pip); + next_delay += ctx->getWireDelay(next_wire).maxDelay(); -#ifdef ARCH_ECP5 - // ECP5 global nets currently appear part-unrouted due to arch database limitations - // Don't touch them in the router - if (net_info->is_global) - return; -#endif - if (net_info->driver.cell == nullptr) - return; + WireId conflictWireWire = WireId(), conflictPipWire = WireId(); + NetInfo *conflictWireNet = nullptr, *conflictPipNet = nullptr; - auto src_wire = ctx->getNetinfoSourceWire(net_info); + if (net_info->wires.count(next_wire) && net_info->wires.at(next_wire).pip == pip) { + next_bonus += cfg.reuseBonus; + } else { + if (!ctx->checkWireAvail(next_wire)) { + if (!ripup) + continue; + conflictWireWire = ctx->getConflictingWireWire(next_wire); + if (conflictWireWire == WireId()) { + conflictWireNet = ctx->getConflictingWireNet(next_wire); + if (conflictWireNet == nullptr) + continue; + } + } - if (src_wire == WireId()) - log_error("No wire found for port %s on source cell %s.\n", net_info->driver.port.c_str(ctx), - net_info->driver.cell->name.c_str(ctx)); + if (!ctx->checkPipAvail(pip)) { + if (!ripup) + continue; + conflictPipWire = ctx->getConflictingPipWire(pip); + if (conflictPipWire == WireId()) { + conflictPipNet = ctx->getConflictingPipNet(pip); + if (conflictPipNet == nullptr) + continue; + } + } - auto &net_cache = cache[net_name]; + if (conflictWireNet != nullptr && conflictPipWire != WireId() && + conflictWireNet->wires.count(conflictPipWire)) + conflictPipWire = WireId(); - if (net_cache.empty()) - net_cache.resize(net_info->users.size()); + if (conflictPipNet != nullptr && conflictWireWire != WireId() && + conflictPipNet->wires.count(conflictWireWire)) + conflictWireWire = WireId(); - for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { - if (net_cache[user_idx]) - continue; + if (conflictWireWire == conflictPipWire) + conflictWireWire = WireId(); - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + if (conflictWireNet == conflictPipNet) + conflictWireNet = nullptr; - if (dst_wire == WireId()) - log_error("No wire found for port %s on destination cell %s.\n", net_info->users[user_idx].port.c_str(ctx), - net_info->users[user_idx].cell->name.c_str(ctx)); + if (conflictWireWire != WireId()) { + auto scores_it = wireScores.find(conflictWireWire); + if (scores_it != wireScores.end()) + next_penalty += scores_it->second * cfg.wireRipupPenalty; + next_penalty += cfg.wireRipupPenalty; + } - WireId cursor = dst_wire; - while (src_wire != cursor) { - auto it = net_info->wires.find(cursor); - if (it == net_info->wires.end()) { - RouteJob job; - job.net = net_name; - job.user_idx = user_idx; - job.slack = net_info->users[user_idx].budget - ctx->estimateDelay(src_wire, dst_wire); - job.randtag = ctx->rng(); - queue.push(job); - net_cache[user_idx] = true; - break; - } - NPNR_ASSERT(it->second.pip != PipId()); - cursor = ctx->getPipSrcWire(it->second.pip); - } - } -} + if (conflictPipWire != WireId()) { + auto scores_it = wireScores.find(conflictPipWire); + if (scores_it != wireScores.end()) + next_penalty += scores_it->second * cfg.wireRipupPenalty; + next_penalty += cfg.wireRipupPenalty; + } -void cleanupReroute(Context *ctx, const Router1Cfg &cfg, RipupScoreboard &scores, - std::unordered_set<IdString> &cleanupQueue, - std::priority_queue<RouteJob, std::vector<RouteJob>, RouteJob::Greater> &jobQueue, - int &totalVisitCnt, int &totalRevisitCnt, int &totalOvertimeRevisitCnt) -{ - std::priority_queue<RouteJob, std::vector<RouteJob>, RouteJob::Greater> cleanupJobs; - std::vector<NetInfo *> allNetinfos; + if (conflictWireNet != nullptr) { + auto scores_it = netScores.find(conflictWireNet); + if (scores_it != netScores.end()) + next_penalty += scores_it->second * cfg.netRipupPenalty; + next_penalty += cfg.netRipupPenalty; + next_penalty += conflictWireNet->wires.size() * cfg.wireRipupPenalty; + } - for (auto net_name : cleanupQueue) { - NetInfo *net_info = ctx->nets.at(net_name).get(); - auto src_wire = ctx->getNetinfoSourceWire(net_info); + if (conflictPipNet != nullptr) { + auto scores_it = netScores.find(conflictPipNet); + if (scores_it != netScores.end()) + next_penalty += scores_it->second * cfg.netRipupPenalty; + next_penalty += cfg.netRipupPenalty; + next_penalty += conflictPipNet->wires.size() * cfg.wireRipupPenalty; + } + } - if (ctx->verbose) - allNetinfos.push_back(net_info); + delay_t next_score = next_delay + next_penalty; + NPNR_ASSERT(next_score >= 0); - std::unordered_map<WireId, int> useCounters; - std::vector<int> candidateArcs; + if ((best_score >= 0) && (next_score - next_bonus - cfg.estimatePrecision > best_score)) + continue; - for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + auto old_visited_it = visited.find(next_wire); + if (old_visited_it != visited.end()) { + delay_t old_delay = old_visited_it->second.delay; + delay_t old_score = old_delay + old_visited_it->second.penalty; + NPNR_ASSERT(old_score >= 0); - if (dst_wire == src_wire) - continue; + if (next_score + ctx->getDelayEpsilon() >= old_score) + continue; - auto cursor = dst_wire; - useCounters[cursor]++; +#if 0 + if (ctx->debug) + log("Found better route to %s. Old vs new delay estimate: %.3f (%.3f) %.3f (%.3f)\n", + ctx->nameOfWire(next_wire), + ctx->getDelayNS(old_score), + ctx->getDelayNS(old_visited_it->second.delay), + ctx->getDelayNS(next_score), + ctx->getDelayNS(next_delay)); +#endif + } - while (cursor != src_wire) { - auto it = net_info->wires.find(cursor); - if (it == net_info->wires.end()) - break; - cursor = ctx->getPipSrcWire(it->second.pip); - useCounters[cursor]++; - } + QueuedWire next_qw; + next_qw.wire = next_wire; + next_qw.pip = pip; + next_qw.delay = next_delay; + next_qw.penalty = next_penalty; + next_qw.bonus = next_bonus; + if (cfg.useEstimate) { + next_qw.togo = ctx->estimateDelay(next_wire, dst_wire); + delay_t this_est = next_qw.delay + next_qw.togo; + if (this_est / 2 - cfg.estimatePrecision > best_est) + continue; + if (best_est > this_est) + best_est = this_est; + } + next_qw.randtag = ctx->rng(); - if (cursor != src_wire) - continue; +#if 0 + if (ctx->debug) + log("%s -> %s: %.3f (%.3f)\n", + ctx->nameOfWire(qw.wire), + ctx->nameOfWire(next_wire), + ctx->getDelayNS(next_score), + ctx->getDelayNS(next_delay)); +#endif - candidateArcs.push_back(user_idx); + visited[next_qw.wire] = next_qw; + queue.push(next_qw); + + if (next_wire == dst_wire) { + maxVisitCnt = std::min(maxVisitCnt, 2 * visitCnt + (next_qw.penalty > 0 ? 100 : 0)); + best_score = next_score - next_bonus; + } + } } - for (int user_idx : candidateArcs) { - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + if (ctx->debug) + log(" total number of visited nodes: %d\n", visitCnt); - if (useCounters.at(dst_wire) != 1) - continue; + if (visited.count(dst_wire) == 0) { + if (ctx->debug) + log(" no route found for this arc\n"); + return false; + } - RouteJob job; - job.net = net_name; - job.user_idx = user_idx; - job.slack = net_info->users[user_idx].budget - ctx->estimateDelay(src_wire, dst_wire); - job.randtag = ctx->rng(); - cleanupJobs.push(job); + if (ctx->debug) { + log(" final route delay: %8.2f\n", ctx->getDelayNS(visited[dst_wire].delay)); + log(" final route penalty: %8.2f\n", ctx->getDelayNS(visited[dst_wire].penalty)); + log(" final route bonus: %8.2f\n", ctx->getDelayNS(visited[dst_wire].bonus)); + log(" arc budget: %12.2f\n", ctx->getDelayNS(net_info->users[user_idx].budget)); } - } - log_info("running cleanup re-route of %d nets (%d arcs).\n", int(cleanupQueue.size()), int(cleanupJobs.size())); + // bind resulting route (and maybe unroute other nets) - cleanupQueue.clear(); + std::unordered_set<WireId> unassign_wires = arc_to_wires[arc]; - int visitCnt = 0, revisitCnt = 0, overtimeRevisitCnt = 0; - int totalWireCountDelta = 0; + WireId cursor = dst_wire; + delay_t accumulated_path_delay = 0; + delay_t last_path_delay_delta = 0; + while (1) { + auto pip = visited[cursor].pip; - if (ctx->verbose) { - for (auto it : allNetinfos) - totalWireCountDelta -= it->wires.size(); - } + if (ctx->debug) { + delay_t path_delay_delta = ctx->estimateDelay(cursor, dst_wire) - accumulated_path_delay; - while (!cleanupJobs.empty()) { - RouteJob job = cleanupJobs.top(); - cleanupJobs.pop(); + log(" node %s (%+.2f %+.2f)\n", ctx->nameOfWire(cursor), ctx->getDelayNS(path_delay_delta), + ctx->getDelayNS(path_delay_delta - last_path_delay_delta)); - auto net_name = job.net; - auto user_idx = job.user_idx; + last_path_delay_delta = path_delay_delta; - NetInfo *net_info = ctx->nets.at(net_name).get(); - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + if (pip != PipId()) + accumulated_path_delay += ctx->getPipDelay(pip).maxDelay(); + accumulated_path_delay += ctx->getWireDelay(cursor).maxDelay(); + } - ctx->unbindWire(dst_wire); + if (pip == PipId()) + NPNR_ASSERT(cursor == src_wire); - Router router(ctx, cfg, scores, net_name, user_idx, false, false); + if (!net_info->wires.count(cursor) || net_info->wires.at(cursor).pip != pip) { + if (!ctx->checkWireAvail(cursor)) { + ripup_wire(cursor); + NPNR_ASSERT(ctx->checkWireAvail(cursor)); + } - if (!router.routedOkay) - log_error("Failed to re-route arc %d of net %s.\n", user_idx, net_name.c_str(ctx)); + if (pip != PipId() && !ctx->checkPipAvail(pip)) { + ripup_pip(pip); + NPNR_ASSERT(ctx->checkPipAvail(pip)); + } - visitCnt += router.visitCnt; - revisitCnt += router.revisitCnt; - overtimeRevisitCnt += router.overtimeRevisitCnt; - } + if (pip == PipId()) { + if (ctx->debug) + log(" bind wire %s\n", ctx->nameOfWire(cursor)); + ctx->bindWire(cursor, net_info, STRENGTH_WEAK); + } else { + if (ctx->debug) + log(" bind pip %s\n", ctx->nameOfPip(pip)); + ctx->bindPip(pip, net_info, STRENGTH_WEAK); + } + } - if (ctx->verbose) { - for (auto it : allNetinfos) - totalWireCountDelta += it->wires.size(); + wire_to_arcs[cursor].insert(arc); + arc_to_wires[arc].insert(cursor); - log_info(" visited %d PIPs (%.2f%% revisits, %.2f%% overtime), %+d wires.\n", visitCnt, - (100.0 * revisitCnt) / visitCnt, (100.0 * overtimeRevisitCnt) / visitCnt, totalWireCountDelta); - } + if (pip == PipId()) + break; - totalVisitCnt += visitCnt; - totalRevisitCnt += revisitCnt; - totalOvertimeRevisitCnt += overtimeRevisitCnt; -} + cursor = ctx->getPipSrcWire(pip); + } + + if (ripup_flag) + arcs_with_ripup++; + else + arcs_without_ripup++; + + return true; + } +}; } // namespace @@ -694,302 +736,265 @@ Router1Cfg::Router1Cfg(Context *ctx) : Settings(ctx) cleanupReroute = get<bool>("router1/cleanupReroute", true); fullCleanupReroute = get<bool>("router1/fullCleanupReroute", true); useEstimate = get<bool>("router1/useEstimate", true); + + wireRipupPenalty = ctx->getRipupDelayPenalty(); + netRipupPenalty = 10 * ctx->getRipupDelayPenalty(); + reuseBonus = wireRipupPenalty / 2; + + estimatePrecision = 100 * ctx->getRipupDelayPenalty(); } bool router1(Context *ctx, const Router1Cfg &cfg) { try { - int totalVisitCnt = 0, totalRevisitCnt = 0, totalOvertimeRevisitCnt = 0; - delay_t ripup_penalty = ctx->getRipupDelayPenalty(); - RipupScoreboard scores; - log_break(); log_info("Routing..\n"); ctx->lock(); - std::unordered_set<IdString> cleanupQueue; - std::unordered_map<IdString, std::vector<bool>> jobCache; - std::priority_queue<RouteJob, std::vector<RouteJob>, RouteJob::Greater> jobQueue; + log_info("Setting up routing queue.\n"); - for (auto &net_it : ctx->nets) - addNetRouteJobs(ctx, cfg, net_it.first, jobCache, jobQueue); + Router1 router(ctx, cfg); + router.setup(); +#ifndef NDEBUG + router.check(); +#endif - if (jobQueue.empty()) { - log_info("found no unrouted source-sink pairs. no routing necessary.\n"); - ctx->unlock(); - return true; - } + log_info("Routing %d arcs.\n", int(router.arc_queue.size())); + + int iter_cnt = 0; + int last_arcs_with_ripup = 0; + int last_arcs_without_ripup = 0; + + log_info(" | (re-)routed arcs | delta | remaining\n"); + log_info(" IterCnt | w/ripup wo/ripup | w/r wo/r | arcs\n"); + + while (!router.arc_queue.empty()) { + if (++iter_cnt % 1000 == 0) { + log_info("%10d | %8d %10d | %4d %5d | %9d\n", iter_cnt, router.arcs_with_ripup, + router.arcs_without_ripup, router.arcs_with_ripup - last_arcs_with_ripup, + router.arcs_without_ripup - last_arcs_without_ripup, int(router.arc_queue.size())); + last_arcs_with_ripup = router.arcs_with_ripup; + last_arcs_without_ripup = router.arcs_without_ripup; +#ifndef NDEBUG + router.check(); +#endif + } - log_info("found %d unrouted source-sink pairs. starting routing procedure.\n", int(jobQueue.size())); + if (ctx->debug) + log("-- %d --\n", iter_cnt); - int iterCnt = 0; + arc_key arc = router.arc_queue_pop(); - while (!jobQueue.empty()) { - if (iterCnt == cfg.maxIterCnt) { - log_warning("giving up after %d iterations.\n", iterCnt); - log_info("Checksum: 0x%08x\n", ctx->checksum()); + if (!router.route_arc(arc, true)) { + log_warning("Failed to find a route for arc %d of net %s.\n", arc.user_idx, ctx->nameOf(arc.net_info)); #ifndef NDEBUG + router.check(); ctx->check(); #endif ctx->unlock(); return false; } + } - iterCnt++; - if (ctx->verbose) - log_info("-- %d --\n", iterCnt); - - int visitCnt = 0, revisitCnt = 0, overtimeRevisitCnt = 0, jobCnt = 0, failedCnt = 0; + log_info("%10d | %8d %10d | %4d %5d | %9d\n", iter_cnt, router.arcs_with_ripup, router.arcs_without_ripup, + router.arcs_with_ripup - last_arcs_with_ripup, router.arcs_without_ripup - last_arcs_without_ripup, + int(router.arc_queue.size())); + log_info("Routing complete.\n"); - std::unordered_set<IdString> normalRouteNets, ripupQueue; +#ifndef NDEBUG + router.check(); + ctx->check(); + log_assert(ctx->checkRoutedDesign()); +#endif - if (ctx->verbose || iterCnt == 1) - log_info("routing queue contains %d jobs.\n", int(jobQueue.size())); - else if (ctx->slack_redist_iter > 0 && iterCnt % ctx->slack_redist_iter == 0) - assign_budget(ctx, true /* quiet */); + log_info("Checksum: 0x%08x\n", ctx->checksum()); + timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */); - bool printNets = ctx->verbose && (jobQueue.size() < 10); + ctx->unlock(); + return true; + } catch (log_execution_error_exception) { +#ifndef NDEBUG + ctx->check(); +#endif + ctx->unlock(); + return false; + } +} - while (!jobQueue.empty()) { - if (ctx->debug) - log("Next job slack: %f\n", double(jobQueue.top().slack)); +bool Context::checkRoutedDesign() const +{ + const Context *ctx = getCtx(); - auto net_name = jobQueue.top().net; - auto user_idx = jobQueue.top().user_idx; - jobQueue.pop(); + for (auto &net_it : ctx->nets) { + NetInfo *net_info = net_it.second.get(); - if (cfg.fullCleanupReroute) - cleanupQueue.insert(net_name); +#ifdef ARCH_ECP5 + if (net_info->is_global) + continue; +#endif - if (printNets) { - if (user_idx < 0) - log_info(" routing all %d users of net %s\n", int(ctx->nets.at(net_name)->users.size()), - net_name.c_str(ctx)); - else - log_info(" routing user %d of net %s\n", user_idx, net_name.c_str(ctx)); - } + if (ctx->debug) + log("checking net %s\n", ctx->nameOf(net_info)); - Router router(ctx, cfg, scores, net_name, user_idx, false, false); + if (net_info->users.empty()) { + if (ctx->debug) + log(" net without sinks\n"); + log_assert(net_info->wires.empty()); + continue; + } - jobCnt++; - visitCnt += router.visitCnt; - revisitCnt += router.revisitCnt; - overtimeRevisitCnt += router.overtimeRevisitCnt; + bool found_unrouted = false; + bool found_loop = false; + bool found_stub = false; - if (!router.routedOkay) { - if (printNets) - log_info(" failed to route to %s.\n", ctx->getWireName(router.failedDest).c_str(ctx)); - ripupQueue.insert(net_name); - failedCnt++; - } else { - normalRouteNets.insert(net_name); - } + struct ExtraWireInfo + { + int order_num = 0; + std::unordered_set<WireId> children; + }; - if ((ctx->verbose || iterCnt == 1) && !printNets && (jobCnt % 100 == 0)) { - log_info(" processed %d jobs. (%d routed, %d failed)\n", jobCnt, jobCnt - failedCnt, failedCnt); - ctx->yield(); - } - } + std::unordered_map<WireId, ExtraWireInfo> db; - NPNR_ASSERT(jobQueue.empty()); - jobCache.clear(); + for (auto &it : net_info->wires) { + WireId w = it.first; + PipId p = it.second.pip; - if ((ctx->verbose || iterCnt == 1) && (jobCnt % 100 != 0)) { - log_info(" processed %d jobs. (%d routed, %d failed)\n", jobCnt, jobCnt - failedCnt, failedCnt); - ctx->yield(); + if (p != PipId()) { + log_assert(ctx->getPipDstWire(p) == w); + db[ctx->getPipSrcWire(p)].children.insert(w); } + } - if (ctx->verbose) - log_info(" visited %d PIPs (%.2f%% revisits, %.2f%% overtime revisits).\n", visitCnt, - (100.0 * revisitCnt) / visitCnt, (100.0 * overtimeRevisitCnt) / visitCnt); - - if (!ripupQueue.empty()) { - if (ctx->verbose || iterCnt == 1) - log_info("failed to route %d nets. re-routing in ripup mode.\n", int(ripupQueue.size())); - - printNets = ctx->verbose && (ripupQueue.size() < 10); - - visitCnt = 0; - revisitCnt = 0; - overtimeRevisitCnt = 0; - int netCnt = 0; - int ripCnt = 0; - - std::vector<IdString> ripupArray(ripupQueue.begin(), ripupQueue.end()); - ctx->sorted_shuffle(ripupArray); - - for (auto net_name : ripupArray) { - if (cfg.cleanupReroute) - cleanupQueue.insert(net_name); - - if (printNets) - log_info(" routing net %s. (%d users)\n", net_name.c_str(ctx), - int(ctx->nets.at(net_name)->users.size())); - - Router router(ctx, cfg, scores, net_name, -1, false, true, ripup_penalty); - - netCnt++; - visitCnt += router.visitCnt; - revisitCnt += router.revisitCnt; - overtimeRevisitCnt += router.overtimeRevisitCnt; + auto src_wire = ctx->getNetinfoSourceWire(net_info); + log_assert(src_wire != WireId()); - if (!router.routedOkay) - log_error("Net %s is impossible to route.\n", net_name.c_str(ctx)); + if (net_info->wires.count(src_wire) == 0) { + if (ctx->debug) + log(" source (%s) not bound to net\n", ctx->nameOfWire(src_wire)); + found_unrouted = true; + } - for (auto it : router.rippedNets) { - addFullNetRouteJob(ctx, cfg, it, jobCache, jobQueue); - if (cfg.cleanupReroute) - cleanupQueue.insert(it); - } + std::unordered_map<WireId, int> dest_wires; + for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { + auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + log_assert(dst_wire != WireId()); + dest_wires[dst_wire] = user_idx; - if (printNets) { - if (router.rippedNets.size() < 10) { - log_info(" ripped up %d other nets:\n", int(router.rippedNets.size())); - for (auto n : router.rippedNets) - log_info(" %s (%d users)\n", n.c_str(ctx), int(ctx->nets.at(n)->users.size())); - } else { - log_info(" ripped up %d other nets.\n", int(router.rippedNets.size())); - } - } + if (net_info->wires.count(dst_wire) == 0) { + if (ctx->debug) + log(" sink %d (%s) not bound to net\n", user_idx, ctx->nameOfWire(dst_wire)); + found_unrouted = true; + } + } - ripCnt += router.rippedNets.size(); + std::function<void(WireId, int)> setOrderNum; + std::unordered_set<WireId> logged_wires; - if ((ctx->verbose || iterCnt == 1) && !printNets && (netCnt % 100 == 0)) { - log_info(" routed %d nets, ripped %d nets.\n", netCnt, ripCnt); - ctx->yield(); - } + setOrderNum = [&](WireId w, int num) { + auto &db_entry = db[w]; + if (db_entry.order_num != 0) { + found_loop = true; + log(" %*s=> loop\n", 2 * num, ""); + return; + } + db_entry.order_num = num; + for (WireId child : db_entry.children) { + if (ctx->debug) { + log(" %*s-> %s\n", 2 * num, "", ctx->nameOfWire(child)); + logged_wires.insert(child); } - - if ((ctx->verbose || iterCnt == 1) && (netCnt % 100 != 0)) - log_info(" routed %d nets, ripped %d nets.\n", netCnt, ripCnt); - - if (ctx->verbose) - log_info(" visited %d PIPs (%.2f%% revisits, %.2f%% overtime revisits).\n", visitCnt, - (100.0 * revisitCnt) / visitCnt, (100.0 * overtimeRevisitCnt) / visitCnt); - - if (ctx->verbose && !jobQueue.empty()) - log_info(" ripped up %d previously routed nets. continue routing.\n", int(jobQueue.size())); + setOrderNum(child, num + 1); } + if (db_entry.children.empty()) { + if (dest_wires.count(w) != 0) { + if (ctx->debug) + log(" %*s=> sink %d\n", 2 * num, "", dest_wires.at(w)); + } else { + if (ctx->debug) + log(" %*s=> stub\n", 2 * num, ""); + found_stub = true; + } + } + }; - if (!ctx->verbose) - log_info("iteration %d: routed %d nets without ripup, routed %d nets with ripup.\n", iterCnt, - int(normalRouteNets.size()), int(ripupQueue.size())); - - totalVisitCnt += visitCnt; - totalRevisitCnt += revisitCnt; - totalOvertimeRevisitCnt += overtimeRevisitCnt; - - if (iterCnt == 8 || iterCnt == 16 || iterCnt == 32 || iterCnt == 64 || iterCnt == 128) - ripup_penalty += ctx->getRipupDelayPenalty(); + if (ctx->debug) { + log(" driver: %s\n", ctx->nameOfWire(src_wire)); + logged_wires.insert(src_wire); + } + setOrderNum(src_wire, 1); - if (jobQueue.empty() || (iterCnt % 5) == 0 || (cfg.fullCleanupReroute && iterCnt == 1)) - cleanupReroute(ctx, cfg, scores, cleanupQueue, jobQueue, totalVisitCnt, totalRevisitCnt, - totalOvertimeRevisitCnt); + std::unordered_set<WireId> dangling_wires; - ctx->yield(); + for (auto &it : db) { + auto &db_entry = it.second; + if (db_entry.order_num == 0) + dangling_wires.insert(it.first); } - log_info("routing complete after %d iterations.\n", iterCnt); + if (ctx->debug) { + if (dangling_wires.empty()) { + log(" no dangling wires.\n"); + } else { + std::unordered_set<WireId> root_wires = dangling_wires; + + for (WireId w : dangling_wires) { + for (WireId c : db[w].children) + root_wires.erase(c); + } - log_info("visited %d PIPs (%.2f%% revisits, %.2f%% overtime revisits).\n", totalVisitCnt, - (100.0 * totalRevisitCnt) / totalVisitCnt, (100.0 * totalOvertimeRevisitCnt) / totalVisitCnt); + for (WireId w : root_wires) { + log(" dangling wire: %s\n", ctx->nameOfWire(w)); + logged_wires.insert(w); + setOrderNum(w, 1); + } - { - float tns = 0; - int tns_net_count = 0; - int tns_arc_count = 0; - for (auto &net_it : ctx->nets) { - bool got_negative_slack = false; - NetInfo *net_info = ctx->nets.at(net_it.first).get(); - for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { - delay_t arc_delay = ctx->getNetinfoRouteDelay(net_info, net_info->users[user_idx]); - delay_t arc_budget = net_info->users[user_idx].budget; - delay_t arc_slack = arc_budget - arc_delay; - if (arc_slack < 0) { - if (!got_negative_slack) { - if (ctx->verbose) - log_info("net %s has negative slack arcs:\n", net_info->name.c_str(ctx)); - tns_net_count++; - } - if (ctx->verbose) - log_info(" arc %s -> %s has %f ns slack (delay %f, budget %f)\n", - ctx->getWireName(ctx->getNetinfoSourceWire(net_info)).c_str(ctx), - ctx->getWireName(ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx])) - .c_str(ctx), - ctx->getDelayNS(arc_slack), ctx->getDelayNS(arc_delay), - ctx->getDelayNS(arc_budget)); - tns += ctx->getDelayNS(arc_slack); - tns_arc_count++; - } + for (WireId w : dangling_wires) { + if (logged_wires.count(w) == 0) + log(" loop: %s -> %s\n", + ctx->nameOfWire(ctx->getPipSrcWire(net_info->wires.at(w).pip)), + ctx->nameOfWire(w)); } } - log_info("final tns with respect to arc budgets: %f ns (%d nets, %d arcs)\n", tns, tns_net_count, - tns_arc_count); } - NPNR_ASSERT(jobQueue.empty()); - jobCache.clear(); + bool fail = false; - for (auto &net_it : ctx->nets) - addNetRouteJobs(ctx, cfg, net_it.first, jobCache, jobQueue); + if (found_unrouted) { + if (ctx->debug) + log("check failed: found unrouted arcs\n"); + fail = true; + } -#ifndef NDEBUG - if (!jobQueue.empty()) { - log_info("Design strangely still contains unrouted source-sink pairs:\n"); - while (!jobQueue.empty()) { - log_info(" user %d on net %s.\n", jobQueue.top().user_idx, jobQueue.top().net.c_str(ctx)); - jobQueue.pop(); - } - log_info("Checksum: 0x%08x\n", ctx->checksum()); - ctx->check(); - ctx->unlock(); - return false; + if (found_loop) { + if (ctx->debug) + log("check failed: found loops\n"); + fail = true; } -#endif - log_info("Checksum: 0x%08x\n", ctx->checksum()); -#ifndef NDEBUG - ctx->check(); -#endif - timing_analysis(ctx, true /* slack_histogram */, true /* print_path */); - ctx->unlock(); - return true; - } catch (log_execution_error_exception) { -#ifndef NDEBUG - ctx->check(); -#endif - ctx->unlock(); - return false; + if (found_stub) { + if (ctx->debug) + log("check failed: found stubs\n"); + fail = true; + } + + if (!dangling_wires.empty()) { + if (ctx->debug) + log("check failed: found dangling wires\n"); + fail = true; + } + + if (fail) + return false; } + + return true; } bool Context::getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t *delay, std::unordered_map<WireId, PipId> *route, bool useEstimate) { - RipupScoreboard scores; - Router1Cfg cfg(this); - cfg.useEstimate = useEstimate; - - Router router(this, cfg, scores, src_wire, dst_wire); - - if (!router.routedOkay) - return false; - - if (delay != nullptr) - *delay = router.visited.at(dst_wire).delay; - - if (route != nullptr) { - WireId cursor = dst_wire; - while (1) { - PipId pip = router.visited.at(cursor).pip; - (*route)[cursor] = pip; - if (pip == PipId()) - break; - cursor = getPipSrcWire(pip); - } - } - - return true; + // FIXME + return false; } NEXTPNR_NAMESPACE_END diff --git a/common/router1.h b/common/router1.h index a184cbe7..80d7aa96 100644 --- a/common/router1.h +++ b/common/router1.h @@ -33,6 +33,10 @@ struct Router1Cfg : Settings bool cleanupReroute; bool fullCleanupReroute; bool useEstimate; + delay_t wireRipupPenalty; + delay_t netRipupPenalty; + delay_t reuseBonus; + delay_t estimatePrecision; }; extern bool router1(Context *ctx, const Router1Cfg &cfg); diff --git a/common/timing.cc b/common/timing.cc index d1a85779..40e4d344 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -22,6 +22,7 @@ #include <algorithm> #include <boost/range/adaptor/reversed.hpp> #include <deque> +#include <map> #include <unordered_map> #include <utility> #include "log.h" @@ -29,17 +30,72 @@ NEXTPNR_NAMESPACE_BEGIN +namespace { +struct ClockEvent +{ + IdString clock; + ClockEdge edge; + + bool operator==(const ClockEvent &other) const { return clock == other.clock && edge == other.edge; } +}; + +struct ClockPair +{ + ClockEvent start, end; + + bool operator==(const ClockPair &other) const { return start == other.start && end == other.end; } +}; +} // namespace + +NEXTPNR_NAMESPACE_END +namespace std { + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockEvent &obj) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(obj.clock)); + boost::hash_combine(seed, hash<int>()(int(obj.edge))); + return seed; + } +}; + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockPair> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockPair &obj) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start)); + boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start)); + return seed; + } +}; + +} // namespace std +NEXTPNR_NAMESPACE_BEGIN + typedef std::vector<const PortRef *> PortRefVector; typedef std::map<int, unsigned> DelayFrequency; +struct CriticalPath +{ + PortRefVector ports; + delay_t path_delay; + delay_t path_period; +}; + +typedef std::unordered_map<ClockPair, CriticalPath> CriticalPathMap; + struct Timing { Context *ctx; bool net_delays; bool update; delay_t min_slack; - PortRefVector *crit_path; + CriticalPathMap *crit_path; DelayFrequency *slack_histogram; + IdString async_clock; struct TimingData { @@ -49,23 +105,24 @@ struct Timing unsigned max_path_length = 0; delay_t min_remaining_budget; bool false_startpoint = false; + std::unordered_map<ClockEvent, delay_t> arrival_time; }; - Timing(Context *ctx, bool net_delays, bool update, PortRefVector *crit_path = nullptr, + Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr, DelayFrequency *slack_histogram = nullptr) : ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq), - crit_path(crit_path), slack_histogram(slack_histogram) + crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$")) { } delay_t walk_paths() { - const auto clk_period = delay_t(1.0e12 / ctx->target_freq); + const auto clk_period = ctx->getDelayFromNS(1.0e9 / ctx->target_freq).maxDelay(); // First, compute the topographical order of nets to walk through the circuit, assuming it is a _acyclic_ graph // TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops std::vector<NetInfo *> topographical_order; - std::unordered_map<const NetInfo *, TimingData> net_data; + std::unordered_map<const NetInfo *, std::unordered_map<ClockEvent, TimingData>> net_data; // In lieu of deleting edges from the graph, simply count the number of fanins to each output port std::unordered_map<const PortInfo *, unsigned> port_fanin; @@ -84,22 +141,34 @@ struct Timing } for (auto o : output_ports) { - IdString clockPort; - TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clockPort); + int clocks = 0; + TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clocks); // If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing // start-point if (portClass == TMG_REGISTER_OUTPUT) { - DelayInfo clkToQ; - ctx->getCellDelay(cell.second.get(), clockPort, o->name, clkToQ); topographical_order.emplace_back(o->net); - net_data.emplace(o->net, TimingData{clkToQ.maxDelay()}); + for (int i = 0; i < clocks; i++) { + TimingClockingInfo clkInfo = ctx->getPortClockingInfo(cell.second.get(), o->name, i); + const NetInfo *clknet = get_net_or_empty(cell.second.get(), clkInfo.clock_port); + IdString clksig = clknet ? clknet->name : async_clock; + net_data[o->net][ClockEvent{clksig, clknet ? clkInfo.edge : RISING_EDGE}] = + TimingData{clkInfo.clockToQ.maxDelay()}; + } + } else { if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) { topographical_order.emplace_back(o->net); TimingData td; td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE); - net_data.emplace(o->net, std::move(td)); + td.max_arrival = 0; + net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td; } + + // Don't analyse paths from a clock input to other pins - they will be considered by the + // special-case handling register input/output class ports + if (portClass == TMG_CLOCK_INPUT) + continue; + // Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and // the current output port, increment fanin counter for (auto i : input_ports) { @@ -120,14 +189,15 @@ struct Timing queue.pop_front(); for (auto &usr : net->users) { - IdString clockPort; - TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort); + int user_clocks; + TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, user_clocks); if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT) continue; for (auto &port : usr.cell->ports) { if (port.second.type != PORT_OUT || !port.second.net) continue; - TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, clockPort); + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, port_clocks); // Skip if this is a clocked output (but allow non-clocked ones) if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE || @@ -166,142 +236,221 @@ struct Timing log_info(" remaining fanin includes %s (no net)\n", fanin.first->name.c_str(ctx)); } } + if (ctx->force) + log_warning("timing analysis failed due to presence of combinatorial loops, incomplete specification of timing ports, etc.\n"); + else + log_error("timing analysis failed due to presence of combinatorial loops, incomplete specification of timing ports, etc.\n"); } - NPNR_ASSERT(port_fanin.empty()); // Go forwards topographically to find the maximum arrival time and max path length for each net for (auto net : topographical_order) { - auto &nd = net_data.at(net); - const auto net_arrival = nd.max_arrival; - const auto net_length_plus_one = nd.max_path_length + 1; - nd.min_remaining_budget = clk_period; - for (auto &usr : net->users) { - IdString clockPort; - TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort); - if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) { - } else { + if (!net_data.count(net)) + continue; + auto &nd_map = net_data.at(net); + for (auto &startdomain : nd_map) { + ClockEvent start_clk = startdomain.first; + auto &nd = startdomain.second; + if (nd.false_startpoint) + continue; + const auto net_arrival = nd.max_arrival; + const auto net_length_plus_one = nd.max_path_length + 1; + nd.min_remaining_budget = clk_period; + for (auto &usr : net->users) { + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks); auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); - auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); auto usr_arrival = net_arrival + net_delay; - // Iterate over all output ports on the same cell as the sink - for (auto port : usr.cell->ports) { - if (port.second.type != PORT_OUT || !port.second.net) - continue; - DelayInfo comb_delay; - // Look up delay through this path - bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); - if (!is_path) - continue; - auto &data = net_data[port.second.net]; - auto &arrival = data.max_arrival; - arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay()); - if (!budget_override) { // Do not increment path length if budget overriden since it doesn't - // require a share of the slack - auto &path_length = data.max_path_length; - path_length = std::max(path_length, net_length_plus_one); + + if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE || + portClass == TMG_CLOCK_INPUT) { + // Skip + } else { + auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); + // Iterate over all output ports on the same cell as the sink + for (auto port : usr.cell->ports) { + if (port.second.type != PORT_OUT || !port.second.net) + continue; + DelayInfo comb_delay; + // Look up delay through this path + bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); + if (!is_path) + continue; + auto &data = net_data[port.second.net][start_clk]; + auto &arrival = data.max_arrival; + arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay()); + if (!budget_override) { // Do not increment path length if budget overriden since it doesn't + // require a share of the slack + auto &path_length = data.max_path_length; + path_length = std::max(path_length, net_length_plus_one); + } } } } } } - const NetInfo *crit_net = nullptr; + std::unordered_map<ClockPair, std::pair<delay_t, NetInfo *>> crit_nets; // Now go backwards topographically to determine the minimum path slack, and to distribute all path slack evenly // between all nets on the path for (auto net : boost::adaptors::reverse(topographical_order)) { - auto &nd = net_data.at(net); - // Ignore false startpoints - if (nd.false_startpoint) continue; - const delay_t net_length_plus_one = nd.max_path_length + 1; - auto &net_min_remaining_budget = nd.min_remaining_budget; - for (auto &usr : net->users) { - auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); - auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); - IdString associatedClock; - TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, associatedClock); - if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) { - const auto net_arrival = nd.max_arrival; - auto path_budget = clk_period - (net_arrival + net_delay); - if (update) { - auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; - usr.budget = std::min(usr.budget, net_delay + budget_share); - net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share); - } + if (!net_data.count(net)) + continue; + auto &nd_map = net_data.at(net); + for (auto &startdomain : nd_map) { + auto &nd = startdomain.second; + // Ignore false startpoints + if (nd.false_startpoint) + continue; + const delay_t net_length_plus_one = nd.max_path_length + 1; + auto &net_min_remaining_budget = nd.min_remaining_budget; + for (auto &usr : net->users) { + auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); + auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks); + if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) { + auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) { + const auto net_arrival = nd.max_arrival; + const auto endpoint_arrival = net_arrival + net_delay + setup; + delay_t period; + // Set default period + if (edge == startdomain.first.edge) { + period = clk_period; + } else { + period = clk_period / 2; + } + if (clksig != async_clock) { + if (ctx->nets.at(clksig)->clkconstr) { + if (edge == startdomain.first.edge) { + // same edge + period = ctx->nets.at(clksig)->clkconstr->period.minDelay(); + } else if (edge == RISING_EDGE) { + // falling -> rising + period = ctx->nets.at(clksig)->clkconstr->low.minDelay(); + } else if (edge == FALLING_EDGE) { + // rising -> falling + period = ctx->nets.at(clksig)->clkconstr->high.minDelay(); + } + } + } + auto path_budget = period - endpoint_arrival; + + if (update) { + auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; + usr.budget = std::min(usr.budget, net_delay + budget_share); + net_min_remaining_budget = + std::min(net_min_remaining_budget, path_budget - budget_share); + } + + if (path_budget < min_slack) + min_slack = path_budget; + + if (slack_histogram) { + int slack_ps = ctx->getDelayNS(path_budget) * 1000; + (*slack_histogram)[slack_ps]++; + } + ClockEvent dest_ev{clksig, edge}; + ClockPair clockPair{startdomain.first, dest_ev}; + nd.arrival_time[dest_ev] = std::max(nd.arrival_time[dest_ev], endpoint_arrival); + + if (crit_path) { + if (!crit_nets.count(clockPair) || crit_nets.at(clockPair).first < endpoint_arrival) { + crit_nets[clockPair] = std::make_pair(endpoint_arrival, net); + (*crit_path)[clockPair].path_delay = endpoint_arrival; + (*crit_path)[clockPair].path_period = period; + (*crit_path)[clockPair].ports.clear(); + (*crit_path)[clockPair].ports.push_back(&usr); + } + } + }; + if (portClass == TMG_REGISTER_INPUT) { + for (int i = 0; i < port_clocks; i++) { + TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, i); + const NetInfo *clknet = get_net_or_empty(usr.cell, clkInfo.clock_port); + IdString clksig = clknet ? clknet->name : async_clock; + process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE, clkInfo.setup.maxDelay()); + } + } else { + process_endpoint(async_clock, RISING_EDGE, 0); + } - if (path_budget < min_slack) { - min_slack = path_budget; - if (crit_path) { - crit_path->clear(); - crit_path->push_back(&usr); - crit_net = net; + } else if (update) { + + // Iterate over all output ports on the same cell as the sink + for (const auto &port : usr.cell->ports) { + if (port.second.type != PORT_OUT || !port.second.net) + continue; + DelayInfo comb_delay; + bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); + if (!is_path) + continue; + if (net_data.count(port.second.net) && + net_data.at(port.second.net).count(startdomain.first)) { + auto path_budget = + net_data.at(port.second.net).at(startdomain.first).min_remaining_budget; + auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; + usr.budget = std::min(usr.budget, net_delay + budget_share); + net_min_remaining_budget = + std::min(net_min_remaining_budget, path_budget - budget_share); + } } } - if (slack_histogram) { - int slack_ps = ctx->getDelayNS(path_budget) * 1000; - (*slack_histogram)[slack_ps]++; - } - } else if (update) { - // Iterate over all output ports on the same cell as the sink - for (const auto &port : usr.cell->ports) { - if (port.second.type != PORT_OUT || !port.second.net) - continue; - DelayInfo comb_delay; - bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); - if (!is_path) - continue; - auto path_budget = net_data.at(port.second.net).min_remaining_budget; - auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; - usr.budget = std::min(usr.budget, net_delay + budget_share); - net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share); - } } } } if (crit_path) { // Walk backwards from the most critical net - while (crit_net) { - const PortInfo *crit_ipin = nullptr; - delay_t max_arrival = std::numeric_limits<delay_t>::min(); - - // Look at all input ports on its driving cell - for (const auto &port : crit_net->driver.cell->ports) { - if (port.second.type != PORT_IN || !port.second.net) - continue; - DelayInfo comb_delay; - bool is_path = - ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay); - if (!is_path) - continue; - // If input port is influenced by a clock, skip - IdString portClock; - TimingPortClass portClass = ctx->getPortTimingClass(crit_net->driver.cell, port.first, portClock); - if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || - portClass == TMG_IGNORE) - continue; + for (auto crit_pair : crit_nets) { + NetInfo *crit_net = crit_pair.second.second; + auto &cp_ports = (*crit_path)[crit_pair.first].ports; + while (crit_net) { + const PortInfo *crit_ipin = nullptr; + delay_t max_arrival = std::numeric_limits<delay_t>::min(); + + // Look at all input ports on its driving cell + for (const auto &port : crit_net->driver.cell->ports) { + if (port.second.type != PORT_IN || !port.second.net) + continue; + DelayInfo comb_delay; + bool is_path = + ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay); + if (!is_path) + continue; + // If input port is influenced by a clock, skip + int port_clocks; + TimingPortClass portClass = + ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks); + if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT || + portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) + continue; - // And find the fanin net with the latest arrival time - const auto net_arrival = net_data.at(port.second.net).max_arrival; - if (net_arrival > max_arrival) { - max_arrival = net_arrival; - crit_ipin = &port.second; + // And find the fanin net with the latest arrival time + if (net_data.count(port.second.net) && + net_data.at(port.second.net).count(crit_pair.first.start)) { + const auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival; + if (net_arrival > max_arrival) { + max_arrival = net_arrival; + crit_ipin = &port.second; + } + } } - } - - if (!crit_ipin) - break; - // Now convert PortInfo* into a PortRef* - for (auto &usr : crit_ipin->net->users) { - if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) { - crit_path->push_back(&usr); + if (!crit_ipin) break; + + // Now convert PortInfo* into a PortRef* + for (auto &usr : crit_ipin->net->users) { + if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) { + cp_ports.push_back(&usr); + break; + } } + crit_net = crit_ipin->net; } - crit_net = crit_ipin->net; + std::reverse(cp_ports.begin(), cp_ports.end()); } - std::reverse(crit_path->begin(), crit_path->end()); } return min_slack; } @@ -362,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); @@ -393,7 +618,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path) auto &driver = net->driver; auto driver_cell = driver.cell; DelayInfo comb_delay; - ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay); + if (last_port == driver.port) { + // Case where we start with a STARTPOINT etc + comb_delay = ctx->getDelayFromNS(0); + } else { + ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay); + } total += comb_delay.maxDelay(); log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total), driver_cell->name.c_str(ctx), driver.port.c_str(ctx)); @@ -404,15 +634,83 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path) log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", ctx->getDelayNS(net_delay), ctx->getDelayNS(total), net->name.c_str(ctx), ctx->getDelayNS(sink->budget), driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y); - log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); + log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); + if (ctx->verbose) { + auto driver_wire = ctx->getNetinfoSourceWire(net); + auto sink_wire = ctx->getNetinfoSinkWire(net, *sink); + log_info(" prediction: %f ns estimate: %f ns\n", + ctx->getDelayNS(ctx->predictDelay(net, *sink)), ctx->getDelayNS(ctx->estimateDelay(driver_wire, sink_wire))); + auto cursor = sink_wire; + delay_t delay; + while (driver_wire != cursor) { + auto it = net->wires.find(cursor); + assert(it != net->wires.end()); + auto pip = it->second.pip; + NPNR_ASSERT(pip != PipId()); + delay = ctx->getPipDelay(pip).maxDelay(); + log_info(" %1.3f %s\n", ctx->getDelayNS(delay), ctx->getPipName(pip).c_str(ctx)); + cursor = ctx->getPipSrcWire(pip); + } + } last_port = sink->port; } + }; + + for (auto &clock : clock_reports) { log_break(); + std::string start = clock.second.first.start.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge"); + std::string end = clock.second.first.end.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge"); + log_info("Critical path report for clock '%s' (%s -> %s):\n", clock.first.c_str(ctx), start.c_str(), end.c_str()); + auto &crit_path = clock.second.second.ports; + print_path_report(clock.second.first, crit_path); + } + + for (auto &xclock : xclock_paths) { + log_break(); + std::string start = format_event(xclock.start); + std::string end = format_event(xclock.end); + log_info("Critical path report for cross-domain path '%s' -> '%s':\n", start.c_str(), end.c_str()); + auto &crit_path = crit_paths.at(xclock).ports; + print_path_report(xclock, crit_path); } } + if (print_fmax) { + log_break(); + unsigned max_width = 0; + for (auto &clock : clock_reports) + max_width = std::max<unsigned>(max_width, clock.first.str(ctx).size()); + for (auto &clock : clock_reports) { + const auto &clock_name = clock.first.str(ctx); + const int width = max_width - clock_name.size(); + if (ctx->nets.at(clock.first)->clkconstr) { + float target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay()); + log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", clock_name.c_str(), + clock_fmax[clock.first], (target < clock_fmax[clock.first]) ? "PASS" : "FAIL", target); + } else { + log_info("Max frequency for clock %*s'%s': %.02f MHz\n", width, "", clock_name.c_str(), clock_fmax[clock.first]); + } + } + for (auto &eclock : empty_clocks) { + if (eclock != ctx->id("$async$")) + log_info("Clock '%s' has no interior paths\n", eclock.c_str(ctx)); + } + log_break(); + + int start_field_width = 0, end_field_width = 0; + for (auto &xclock : xclock_paths) { + start_field_width = std::max((int)format_event(xclock.start).length(), start_field_width); + end_field_width = std::max((int)format_event(xclock.end).length(), end_field_width); + } - delay_t default_slack = delay_t((1.0e9 / ctx->getDelayNS(1)) / ctx->target_freq); - log_info("estimated Fmax = %.2f MHz\n", 1e3 / ctx->getDelayNS(default_slack - min_slack)); + for (auto &xclock : xclock_paths) { + const ClockEvent &a = xclock.start; + const ClockEvent &b = xclock.end; + auto &path = crit_paths.at(xclock); + auto ev_a = format_event(a, start_field_width), ev_b = format_event(b, end_field_width); + log_info("Max delay %s -> %s: %0.02f ns\n", ev_a.c_str(), ev_b.c_str(), ctx->getDelayNS(path.path_delay)); + } + log_break(); + } if (print_histogram && slack_histogram.size() > 0) { unsigned num_bins = 20; diff --git a/common/timing.h b/common/timing.h index cfb71ae0..1fd76310 100644 --- a/common/timing.h +++ b/common/timing.h @@ -29,7 +29,7 @@ void assign_budget(Context *ctx, bool quiet = false); // Perform timing analysis and print out the fmax, and optionally the // critical path -void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_path = false); +void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false); NEXTPNR_NAMESPACE_END |