diff options
54 files changed, 3244 insertions, 1277 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 diff --git a/docs/archapi.md b/docs/archapi.md index 73443c15..3c938865 100644 --- a/docs/archapi.md +++ b/docs/archapi.md @@ -30,15 +30,15 @@ delay_t maxDelay() const { return delay; } ### BelId -A type representing a bel name. `BelId()` must construct a unique null-value. Must provide `==` and `!=` operators and a specialization for `std::hash<BelId>`. +A type representing a bel name. `BelId()` must construct a unique null-value. Must provide `==`, `!=`, and `<` operators and a specialization for `std::hash<BelId>`. ### WireId -A type representing a wire name. `WireId()` must construct a unique null-value. Must provide `==` and `!=` operators and a specialization for `std::hash<WireId>`. +A type representing a wire name. `WireId()` must construct a unique null-value. Must provide `==`, `!=`, and `<` operators and a specialization for `std::hash<WireId>`. ### PipId -A type representing a pip name. `PipId()` must construct a unique null-value. Must provide `==` and `!=` operators and a specialization for `std::hash<PipId>`. +A type representing a pip name. `PipId()` must construct a unique null-value. Must provide `==`, `!=`, and `<` operators and a specialization for `std::hash<PipId>`. ### GroupId @@ -215,14 +215,15 @@ Return true if the wire is available, i.e. can be bound to a net. Return the net a wire is bound to. -### NetInfo \*getConflictingWireNet(WireId wire) const +### WireId getConflictingWireWire(WireId wire) const -If this returns a non-nullptr, then unbinding that net +If this returns a non-WireId(), then unbinding that wire will make the given wire available. -This returns nullptr if the wire is already available, -or if there is no single net that can be unbound to make this -wire available. +### NetInfo \*getConflictingWireNet(WireId wire) const + +If this returns a non-nullptr, then unbinding that entire net +will make the given wire available. ### DelayInfo getWireDelay(WireId wire) const @@ -282,18 +283,23 @@ This method must also update `NetInfo::wires`. Returns true if the given pip is available to be bound to a net. +Users must also check if the pip destination wire is available +with `checkWireAvail(getPipDstWire(pip))` before binding the +pip to a net. + ### NetInfo \*getBoundPipNet(PipId pip) const Return the net this pip is bound to. -### NetInfo \*getConflictingPipNet(PipId pip) const +### WireId getConflictingPipWire(PipId pip) const -Return the net that needs to be unbound in order to make this -pip available. +If this returns a non-WireId(), then unbinding that wire +will make the given pip available. -This does not need to (but may) return the conflicting wire if the conflict is -limited to the conflicting wire being bound to the destination wire for this -pip. +### NetInfo \*getConflictingPipNet(PipId pip) const + +If this returns a non-nullptr, then unbinding that entire net +will make the given pip available. ### const\_range\<PipId\> getPips() const @@ -398,6 +404,10 @@ actual penalty used is a multiple of this value (i.e. a weighted version of this Convert an `delay_t` to an actual real-world delay in nanoseconds. +### DelayInfo getDelayFromNS(float v) const + +Convert a real-world delay in nanoseconds to a DelayInfo with equal min/max rising/falling values. + ### uint32\_t getDelayChecksum(delay\_t v) const Convert a `delay_t` to an integer for checksum calculations. @@ -455,11 +465,17 @@ Cell Delay Methods Returns the delay for the specified path through a cell in the `&delay` argument. The method returns false if there is no timing relationship from `fromPort` to `toPort`. -### TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const +### TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const Return the _timing port class_ of a port. This can be a register or combinational input or output; clock input or -output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockPort is set -to the associated clock port. +output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockInfoCount is set +to the number of associated _clock edges_ that can be queried by getPortClockingInfo. + +### TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const + +Return the _clocking info_ (including port name of clock, clock polarity and setup/hold/clock-to-out times) of a +port. Where ports have more than one clock edge associated with them (such as DDR outputs), `index` can be used to obtain +information for all edges. `index` must be in [0, clockInfoCount), behaviour is undefined otherwise. Placer Methods -------------- diff --git a/docs/constraints.md b/docs/constraints.md new file mode 100644 index 00000000..263df7b6 --- /dev/null +++ b/docs/constraints.md @@ -0,0 +1,37 @@ +# Constraints + +There are three types of constraints available for end users of nextpnr. + +## Architecture-specific IO Cconstraints + +Architectures may provide support for their native (or any other) IO constraint format. +The iCE40 architecture supports PCF constraints thus: + + set_io led[0] 3 + +and the ECP5 architecture supports a subset of LPF constraints: + + LOCATE COMP "led[0]" SITE "E16"; + IOBUF PORT "led[0]" IO_TYPE=LVCMOS25; + + +## Absolute Placement Constraints + +nextpnr provides generic support for placement constraints by setting the Bel attribute on the cell to the name of +the Bel you wish it to be placed at. For example: + + (* BEL="X2/Y5/lc0" *) + +## Clock Constraints + +There are two ways to apply clock constraints in nextpnr. The `--clock {freq}` command line argument is used to +apply a default frequency (in MHz) to all clocks without a more specific constraint. + +The Python API can apply clock constraints to specific named clocks. This is done by passing a Python file +specifying these constraints to the `--pre-pack` command line argument. Inside the file, constraints are applied by +calling the function `ctx.addClock` with the name of the clock and its frequency in MHz, for example: + + ctx.addClock("csi_rx_i.dphy_clk", 96) + ctx.addClock("video_clk", 24) + ctx.addClock("uart_i.sys_clk_i", 12) + diff --git a/docs/faq.md b/docs/faq.md index d440bba6..7b358187 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -38,7 +38,94 @@ For nextpnr we are using the following terminology. Adding new architectures to nextpnr ----------------------------------- -TBD +### Implementing new architectures + +Each nextpnr architecture must implement the *nextpnr architecture API*. +See [archapi.md](archapi.md) for a complete reference of the architecture API. + +### Delay Estimates + +Each architecture must implement a `estimateDelay()` method that estimates the expected delay for a path from given `src` to `dst` wires. +*It is very important that this method slightly overestimates the expected delay.* Furthermore, it should overestimate the expected delay +by a slightly larger margin for longer paths than for shorter paths. Otherwise there will be performance issues with the router. + +The delays estimates returned by that method should also be as fine-grain as possible. It definitely pays off to spend some time improving the `estimateDelay()` +for your architecture once implementing small designs work. + +### Ripup Information + +The `getConflictingWireWire()`, `getConflictingWireNet()`, `getConflictingPipWire()`, and `getConflictingPipNet()` methods are used by the router +to determine which resources to rip up in order to make a given routing resource (wire or pip) available. + +The architecture must guanrantee that the following invariants hold. + +**Invariant 1:** + +``` + if (!ctx->checkWireAvail(wire)) { + WireId w = getConflictingWireWire(wire); + if (w != WireId()) { + ctx->unbindWire(w); + assert(ctx->checkWireAvail(wire)); + } + } +``` + +**Invariant 2:** + +``` + if (!ctx->checkWireAvail(wire)) { + NetInfo *n = getConflictingWireNet(wire); + if (n != nullptr) { + for (auto &it : n->wires) + ctx->unbindWire(it.first); + assert(ctx->checkWireAvail(wire)); + } + } +``` + +**Invariant 3:** + +``` + if (!ctx->checkPipAvail(pip)) { + WireId w = getConflictingPipWire(pip); + if (w != WireId()) { + ctx->unbindWire(w); + assert(ctx->checkPipAvail(pip)); + } + } +``` + +**Invariant 4:** + +``` + if (!ctx->checkPipAvail(pip)) { + NetInfo *n = getConflictingPipNet(pip); + if (n != nullptr) { + for (auto &it : n->wires) + ctx->unbindWire(it.first); + assert(ctx->checkPipAvail(pip)); + } + } +``` + +**Invariant 5:** + +``` + if (ctx->checkWireAvail(wire)) { + // bind is guaranteed to succeed + ctx->bindWire(wire, net, strength); + } +``` + +**Invariant 6:** + +``` + if (ctx->checkPipAvail(pip) && ctx->checkWireAvail(ctx->getPipDstWire(pip))) { + // bind is guaranteed to succeed + ctx->bindPip(pip, net, strength); + } +``` Nextpnr and other tools ----------------------- diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 4a0b31b5..afea8d4a 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -400,7 +400,7 @@ BelId Arch::getBelByLocation(Loc loc) const delay_t Arch::estimateDelay(WireId src, WireId dst) const { - return 100 * (abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y)); + return 170 * (abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y)); } delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const @@ -409,7 +409,7 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const auto driver_loc = getBelLocation(driver.cell->bel); auto sink_loc = getBelLocation(sink.cell->bel); - return 100 * (abs(driver_loc.x - sink_loc.x) + abs(driver_loc.y - sink_loc.y)); + return 170 * (abs(driver_loc.x - sink_loc.x) + abs(driver_loc.y - sink_loc.y)); } bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; } @@ -456,7 +456,7 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const el.x1 = bel.location.x + logic_cell_x1; el.x2 = bel.location.x + logic_cell_x2; el.y1 = bel.location.y + logic_cell_y1 + (2 * z) * logic_cell_pitch; - el.y2 = bel.location.y + logic_cell_y2 + (2 * z + 1) * logic_cell_pitch; + el.y2 = bel.location.y + logic_cell_y2 + (2 * z + 0.5f) * logic_cell_pitch; ret.push_back(el); } } @@ -539,10 +539,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort return true; } #if 0 // FIXME - if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) { - delay.delay = 717; - return true; - } + if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) { + delay.delay = 717; + return true; + } #endif if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) || (fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) || @@ -576,10 +576,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort } } -TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const +TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const { auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; }; - + clockInfoCount = 0; if (cell->type == id_TRELLIS_SLICE) { int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0); if (port == id_CLK || port == id_WCK) @@ -598,13 +598,13 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id return TMG_COMB_OUTPUT; if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) || (sd1 == 1 && port == id_M1)) { - clockPort = id_CLK; + clockInfoCount = 1; return TMG_REGISTER_INPUT; } if (port == id_M0 || port == id_M1) return TMG_COMB_INPUT; if (port == id_Q0 || port == id_Q1) { - clockPort = id_CLK; + clockInfoCount = 1; return TMG_REGISTER_OUTPUT; } @@ -614,7 +614,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 || port == id_WAD3 || port == id_WRE) { - clockPort = id_WCK; + clockInfoCount = 1; return TMG_REGISTER_INPUT; } @@ -638,10 +638,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id for (auto c : boost::adaptors::reverse(port_name)) { if (std::isdigit(c)) continue; - if (c == 'A') - clockPort = id_CLKA; - else if (c == 'B') - clockPort = id_CLKB; + if (c == 'A' || c == 'B') + clockInfoCount = 1; else NPNR_ASSERT_FALSE_STR("bad ram port"); return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT; @@ -653,11 +651,92 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id return TMG_IGNORE; // FIXME } else if (cell->type == id_EHXPLLL) { return TMG_IGNORE; + } else if (cell->type == id_DCUA || cell->type == id_EXTREFB || cell->type == id_PCSCLKDIV) { + if (port == id_CH0_FF_TXI_CLK || port == id_CH0_FF_RXI_CLK || port == id_CH1_FF_TXI_CLK || + port == id_CH1_FF_RXI_CLK) + return TMG_CLOCK_INPUT; + std::string prefix = port.str(this).substr(0, 9); + if (prefix == "CH0_FF_TX" || prefix == "CH0_FF_RX" || prefix == "CH1_FF_TX" || prefix == "CH1_FF_RX") { + clockInfoCount = 1; + return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT; + } + return TMG_IGNORE; } else { NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'"); } } +TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const +{ + TimingClockingInfo info; + info.setup.delay = 0; + info.hold.delay = 0; + info.clockToQ.delay = 0; + if (cell->type == id_TRELLIS_SLICE) { + int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0); + + if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 || + port == id_WAD3 || port == id_WRE) { + info.edge = RISING_EDGE; + info.clock_port = id_WCK; + info.setup.delay = 100; + info.hold.delay = 0; + } else if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) || + (sd1 == 1 && port == id_M1)) { + info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE; + info.clock_port = id_CLK; + info.setup.delay = 100; + info.hold.delay = 0; + } else { + info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE; + info.clock_port = id_CLK; + info.clockToQ.delay = 395; + } + } else if (cell->type == id_DP16KD) { + std::string port_name = port.str(this); + for (auto c : boost::adaptors::reverse(port_name)) { + if (std::isdigit(c)) + continue; + if (c == 'A') { + info.clock_port = id_CLKA; + break; + } else if (c == 'B') { + info.clock_port = id_CLKB; + break; + } else + NPNR_ASSERT_FALSE_STR("bad ram port " + port.str(this)); + } + info.edge = (str_or_default(cell->params, info.clock_port == id_CLKB ? id("CLKBMUX") : id("CLKAMUX"), "CLK") == + "INV") + ? FALLING_EDGE + : RISING_EDGE; + if (cell->ports.at(port).type == PORT_OUT) { + info.clockToQ.delay = 4280; + } else { + info.setup.delay = 100; + info.hold.delay = 0; + } + } else if (cell->type == id_DCUA) { + std::string prefix = port.str(this).substr(0, 9); + info.edge = RISING_EDGE; + if (prefix == "CH0_FF_TX") + info.clock_port = id_CH0_FF_TXI_CLK; + else if (prefix == "CH0_FF_RX") + info.clock_port = id_CH0_FF_RXI_CLK; + else if (prefix == "CH1_FF_TX") + info.clock_port = id_CH1_FF_TXI_CLK; + else if (prefix == "CH1_FF_RX") + info.clock_port = id_CH1_FF_RXI_CLK; + if (cell->ports.at(port).type == PORT_OUT) { + info.clockToQ.delay = 660; + } else { + info.setup.delay = 1000; + info.hold.delay = 0; + } + } + return info; +} + std::vector<std::pair<std::string, std::string>> Arch::getTilesAtLocation(int row, int col) { std::vector<std::pair<std::string, std::string>> ret; diff --git a/ecp5/arch.h b/ecp5/arch.h index 583d539f..aa3c5348 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -619,6 +619,8 @@ struct Arch : BaseCtx return wire_to_net.at(wire); } + WireId getConflictingWireWire(WireId wire) const { return wire; } + NetInfo *getConflictingWireNet(WireId wire) const { NPNR_ASSERT(wire != WireId()); @@ -724,6 +726,8 @@ struct Arch : BaseCtx return pip_to_net.at(pip); } + WireId getConflictingPipWire(PipId pip) const { return WireId(); } + NetInfo *getConflictingPipNet(PipId pip) const { NPNR_ASSERT(pip != PipId()); @@ -855,6 +859,12 @@ struct Arch : BaseCtx delay_t getDelayEpsilon() const { return 20; } delay_t getRipupDelayPenalty() const { return 200; } float getDelayNS(delay_t v) const { return v * 0.001; } + DelayInfo getDelayFromNS(float ns) const + { + DelayInfo del; + del.delay = delay_t(ns * 1000); + return del; + } uint32_t getDelayChecksum(delay_t v) const { return v; } bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; @@ -878,8 +888,10 @@ struct Arch : BaseCtx // Get the delay through a cell from one port to another, returning false // if no path exists bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; - // Get the port class, also setting clockPort if applicable - TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const; + // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port + TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const; + // Get the TimingClockingInfo of a port + TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const; // Return true if a port is a net bool isGlobalNet(const NetInfo *net) const; diff --git a/ecp5/arch_place.cc b/ecp5/arch_place.cc index 6fcd8bde..41f87cb8 100644 --- a/ecp5/arch_place.cc +++ b/ecp5/arch_place.cc @@ -101,6 +101,8 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const bel_cells.push_back(cell); return slicesCompatible(bel_cells); + } else if (cell->type == id_DCUA || cell->type == id_EXTREFB || cell->type == id_PCSCLKDIV) { + return args.type != ArchArgs::LFE5U_25F && args.type != ArchArgs::LFE5U_45F && args.type != ArchArgs::LFE5U_85F; } else { // other checks return true; diff --git a/ecp5/arch_pybindings.cc b/ecp5/arch_pybindings.cc index 9312b4ad..5e73a673 100644 --- a/ecp5/arch_pybindings.cc +++ b/ecp5/arch_pybindings.cc @@ -130,6 +130,10 @@ void arch_wrap_python() "cells"); readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls, "nets"); + + fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>, + pass_through<float>>::def_wrap(ctx_cls, "addClock"); + WRAP_RANGE(Bel, conv_to_str<BelId>); WRAP_RANGE(Wire, conv_to_str<WireId>); WRAP_RANGE(AllPip, conv_to_str<PipId>); diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h index b85852c2..b2c23134 100644 --- a/ecp5/archdefs.h +++ b/ecp5/archdefs.h @@ -75,6 +75,7 @@ struct Location bool operator==(const Location &other) const { return x == other.x && y == other.y; } bool operator!=(const Location &other) const { return x != other.x || y != other.y; } + bool operator<(const Location &other) const { return y == other.y ? x < other.x : y < other.y; } }; inline Location operator+(const Location &a, const Location &b) { return Location(a.x + b.x, a.y + b.y); } @@ -86,6 +87,10 @@ struct BelId bool operator==(const BelId &other) const { return index == other.index && location == other.location; } bool operator!=(const BelId &other) const { return index != other.index || location != other.location; } + bool operator<(const BelId &other) const + { + return location == other.location ? index < other.index : location < other.location; + } }; struct WireId @@ -95,6 +100,10 @@ struct WireId bool operator==(const WireId &other) const { return index == other.index && location == other.location; } bool operator!=(const WireId &other) const { return index != other.index || location != other.location; } + bool operator<(const WireId &other) const + { + return location == other.location ? index < other.index : location < other.location; + } }; struct PipId @@ -104,6 +113,10 @@ struct PipId bool operator==(const PipId &other) const { return index == other.index && location == other.location; } bool operator!=(const PipId &other) const { return index != other.index || location != other.location; } + bool operator<(const PipId &other) const + { + return location == other.location ? index < other.index : location < other.location; + } }; struct GroupId diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 1c150ae2..961a3956 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -24,7 +24,7 @@ #include <queue> #include <regex> #include <streambuf> - +#include <boost/algorithm/string/predicate.hpp> #include "config.h" #include "pio.h" #include "log.h" @@ -379,12 +379,23 @@ std::vector<std::string> get_dsp_tiles(Context *ctx, BelId bel) return tiles; } +// Get the list of tiles corresponding to a DCU +std::vector<std::string> get_dcu_tiles(Context *ctx, BelId bel) +{ + std::vector<std::string> tiles; + Loc loc = ctx->getBelLocation(bel); + for (int i = 0; i < 9; i++) + tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + i, "DCU" + std::to_string(i))); + return tiles; +} + // Get the list of tiles corresponding to a PLL std::vector<std::string> get_pll_tiles(Context *ctx, BelId bel) { std::string name = ctx->locInfo(bel)->bel_data[bel.index].name.get(); std::vector<std::string> tiles; Loc loc = ctx->getBelLocation(bel); + static const std::set<std::string> pll1_lr = {"PLL1_LR", "BANKREF4"}; if (name == "EHXPLL_UL") { tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x - 1, "PLL0_UL")); @@ -394,7 +405,7 @@ std::vector<std::string> get_pll_tiles(Context *ctx, BelId bel) tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x + 1, "BANKREF8")); } else if (name == "EHXPLL_LR") { tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x, "PLL0_LR")); - tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x - 1, "PLL1_LR")); + tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x - 1, pll1_lr)); } else if (name == "EHXPLL_UR") { tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x - 1, "PLL0_UR")); tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x - 1, "PLL1_UR")); @@ -407,26 +418,23 @@ std::vector<std::string> get_pll_tiles(Context *ctx, BelId bel) void fix_tile_names(Context *ctx, ChipConfig &cc) { // Remove the V prefix/suffix on certain tiles if device is a SERDES variant - if (ctx->args.type == ArchArgs::LFE5UM_25F || ctx->args.type == ArchArgs::LFE5UM_45F || - ctx->args.type == ArchArgs::LFE5UM_85F || ctx->args.type == ArchArgs::LFE5UM5G_25F || - ctx->args.type == ArchArgs::LFE5UM5G_45F || ctx->args.type == ArchArgs::LFE5UM5G_85F) { + if (ctx->args.type == ArchArgs::LFE5U_25F || ctx->args.type == ArchArgs::LFE5U_45F || + ctx->args.type == ArchArgs::LFE5U_85F) { std::map<std::string, std::string> tiletype_xform; for (const auto &tile : cc.tiles) { std::string newname = tile.first; - auto vcib = tile.first.find("VCIB"); - if (vcib != std::string::npos) { - // Remove the V - newname.erase(vcib, 1); + auto cibdcu = tile.first.find("CIB_DCU"); + if (cibdcu != std::string::npos) { + // Add the V + if (newname.at(cibdcu - 1) != 'V') + newname.insert(cibdcu, 1, 'V'); + tiletype_xform[tile.first] = newname; + } else if (boost::ends_with(tile.first, "BMID_0H")) { + newname.back() = 'V'; + tiletype_xform[tile.first] = newname; + } else if (boost::ends_with(tile.first, "BMID_2")) { + newname.push_back('V'); tiletype_xform[tile.first] = newname; - } else if (tile.first.back() == 'V') { - // BMID_0V or BMID_2V - if (tile.first.at(tile.first.size() - 2) == '0') { - newname.at(tile.first.size() - 1) = 'H'; - tiletype_xform[tile.first] = newname; - } else if (tile.first.at(tile.first.size() - 2) == '2') { - newname.pop_back(); - tiletype_xform[tile.first] = newname; - } } } // Apply the name changes @@ -455,6 +463,20 @@ void tieoff_dsp_ports(Context *ctx, ChipConfig &cc, CellInfo *ci) } } +void tieoff_dcu_ports(Context *ctx, ChipConfig &cc, CellInfo *ci) +{ + for (auto port : ci->ports) { + if (port.second.net == nullptr && port.second.type == PORT_IN) { + if (port.first.str(ctx).find("CLK") != std::string::npos || + port.first.str(ctx).find("HDIN") != std::string::npos || + port.first.str(ctx).find("HDOUT") != std::string::npos) + continue; + bool value = bool_or_default(ci->params, ctx->id(port.first.str(ctx) + "MUX"), false); + tie_cib_signal(ctx, cc, ctx->getBelPinWire(ci->bel, port.first), value); + } + } +} + static void set_pip(Context *ctx, ChipConfig &cc, PipId pip) { std::string tile = ctx->getPipTilename(pip); @@ -463,6 +485,46 @@ static void set_pip(Context *ctx, ChipConfig &cc, PipId pip) cc.tiles[tile].add_arc(sink, source); } +static std::vector<bool> parse_config_str(std::string str, int length) +{ + // For DCU config which might be bin, hex or dec using prefices accordingly + std::string base = str.substr(0, 2); + std::vector<bool> word; + word.resize(length, false); + if (base == "0b") { + for (int i = 0; i < int(str.length()) - 2; i++) { + char c = str.at((str.size() - 1) - i); + NPNR_ASSERT(c == '0' || c == '1'); + word.at(i) = (c == '1'); + } + } else if (base == "0x") { + for (int i = 0; i < int(str.length()) - 2; i++) { + char c = str.at((str.size() - i) - 1); + int nibble = chtohex(c); + word.at(i * 4) = nibble & 0x1; + if (i * 4 + 1 < length) + word.at(i * 4 + 1) = nibble & 0x2; + if (i * 4 + 2 < length) + word.at(i * 4 + 2) = nibble & 0x4; + if (i * 4 + 3 < length) + word.at(i * 4 + 3) = nibble & 0x8; + } + } else if (base == "0d") { + NPNR_ASSERT(length < 64); + unsigned long long value = std::stoull(str.substr(2)); + for (int i = 0; i < length; i++) + if (value & (1 << i)) + word.at(i) = true; + } else { + NPNR_ASSERT(length < 64); + unsigned long long value = std::stoull(str); + for (int i = 0; i < length; i++) + if (value & (1 << i)) + word.at(i) = true; + } + return word; +} + void write_bitstream(Context *ctx, std::string base_config_file, std::string text_config_file) { ChipConfig cc; @@ -480,6 +542,23 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex // TODO: .bit metadata } + // Clear out DCU tieoffs in base config if DCU used + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_DCUA) { + Loc loc = ctx->getBelLocation(ci->bel); + for (int i = 0; i < 12; i++) { + auto tiles = ctx->getTilesAtLocation(loc.y - 1, loc.x + i); + for (const auto &tile : tiles) { + auto cc_tile = cc.tiles.find(tile.first); + if (cc_tile != cc.tiles.end()) { + cc_tile->second.cenums.clear(); + cc_tile->second.cunknowns.clear(); + } + } + } + } + } // Add all set, configurable pips to the config for (auto pip : ctx->getPips()) { if (ctx->getBoundPipNet(pip) != nullptr) { @@ -999,6 +1078,28 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex int_to_bitvector(int_or_default(ci->attrs, ctx->id("MFG_ENABLE_FILTEROPAMP"), 0), 1)); cc.tilegroups.push_back(tg); + } else if (ci->type == id_DCUA) { + TileGroup tg; + tg.tiles = get_dcu_tiles(ctx, ci->bel); + tg.config.add_enum("DCU.MODE", "DCUA"); +#include "dcu_bitstream.h" + cc.tilegroups.push_back(tg); + tieoff_dcu_ports(ctx, cc, ci); + } else if (ci->type == id_EXTREFB) { + TileGroup tg; + tg.tiles = get_dcu_tiles(ctx, ci->bel); + tg.config.add_word("EXTREF.REFCK_DCBIAS_EN", + parse_config_str(str_or_default(ci->params, ctx->id("REFCK_DCBIAS_EN"), "0"), 1)); + tg.config.add_word("EXTREF.REFCK_RTERM", + parse_config_str(str_or_default(ci->params, ctx->id("REFCK_RTERM"), "0"), 1)); + tg.config.add_word("EXTREF.REFCK_PWDNB", + parse_config_str(str_or_default(ci->params, ctx->id("REFCK_PWDNB"), "0"), 1)); + cc.tilegroups.push_back(tg); + } else if (ci->type == id_PCSCLKDIV) { + Loc loc = ctx->getBelLocation(ci->bel); + std::string tname = ctx->getTileByTypeAndLocation(loc.y + 1, loc.x, "BMID_0H"); + cc.tiles[tname].add_enum("PCSCLKDIV" + std::to_string(loc.z), + str_or_default(ci->params, ctx->id("GSR"), "ENABLED")); } else { NPNR_ASSERT_FALSE("unsupported cell type"); } diff --git a/ecp5/constids.inc b/ecp5/constids.inc index bdcbc1ea..11ecc240 100644 --- a/ecp5/constids.inc +++ b/ecp5/constids.inc @@ -810,3 +810,310 @@ X(LOCK) X(INTLOCK) X(REFCLK) X(CLKINTFB) + + +X(EXTREFB) +X(REFCLKP) +X(REFCLKN) +X(REFCLKO) + +X(DCUA) +X(CH0_HDINP) +X(CH1_HDINP) +X(CH0_HDINN) +X(CH1_HDINN) +X(D_TXBIT_CLKP_FROM_ND) +X(D_TXBIT_CLKN_FROM_ND) +X(D_SYNC_ND) +X(D_TXPLL_LOL_FROM_ND) +X(CH0_RX_REFCLK) +X(CH1_RX_REFCLK) +X(CH0_FF_RXI_CLK) +X(CH1_FF_RXI_CLK) +X(CH0_FF_TXI_CLK) +X(CH1_FF_TXI_CLK) +X(CH0_FF_EBRD_CLK) +X(CH1_FF_EBRD_CLK) +X(CH0_FF_TX_D_0) +X(CH1_FF_TX_D_0) +X(CH0_FF_TX_D_1) +X(CH1_FF_TX_D_1) +X(CH0_FF_TX_D_2) +X(CH1_FF_TX_D_2) +X(CH0_FF_TX_D_3) +X(CH1_FF_TX_D_3) +X(CH0_FF_TX_D_4) +X(CH1_FF_TX_D_4) +X(CH0_FF_TX_D_5) +X(CH1_FF_TX_D_5) +X(CH0_FF_TX_D_6) +X(CH1_FF_TX_D_6) +X(CH0_FF_TX_D_7) +X(CH1_FF_TX_D_7) +X(CH0_FF_TX_D_8) +X(CH1_FF_TX_D_8) +X(CH0_FF_TX_D_9) +X(CH1_FF_TX_D_9) +X(CH0_FF_TX_D_10) +X(CH1_FF_TX_D_10) +X(CH0_FF_TX_D_11) +X(CH1_FF_TX_D_11) +X(CH0_FF_TX_D_12) +X(CH1_FF_TX_D_12) +X(CH0_FF_TX_D_13) +X(CH1_FF_TX_D_13) +X(CH0_FF_TX_D_14) +X(CH1_FF_TX_D_14) +X(CH0_FF_TX_D_15) +X(CH1_FF_TX_D_15) +X(CH0_FF_TX_D_16) +X(CH1_FF_TX_D_16) +X(CH0_FF_TX_D_17) +X(CH1_FF_TX_D_17) +X(CH0_FF_TX_D_18) +X(CH1_FF_TX_D_18) +X(CH0_FF_TX_D_19) +X(CH1_FF_TX_D_19) +X(CH0_FF_TX_D_20) +X(CH1_FF_TX_D_20) +X(CH0_FF_TX_D_21) +X(CH1_FF_TX_D_21) +X(CH0_FF_TX_D_22) +X(CH1_FF_TX_D_22) +X(CH0_FF_TX_D_23) +X(CH1_FF_TX_D_23) +X(CH0_FFC_EI_EN) +X(CH1_FFC_EI_EN) +X(CH0_FFC_PCIE_DET_EN) +X(CH1_FFC_PCIE_DET_EN) +X(CH0_FFC_PCIE_CT) +X(CH1_FFC_PCIE_CT) +X(CH0_FFC_SB_INV_RX) +X(CH1_FFC_SB_INV_RX) +X(CH0_FFC_ENABLE_CGALIGN) +X(CH1_FFC_ENABLE_CGALIGN) +X(CH0_FFC_SIGNAL_DETECT) +X(CH1_FFC_SIGNAL_DETECT) +X(CH0_FFC_FB_LOOPBACK) +X(CH1_FFC_FB_LOOPBACK) +X(CH0_FFC_SB_PFIFO_LP) +X(CH1_FFC_SB_PFIFO_LP) +X(CH0_FFC_PFIFO_CLR) +X(CH1_FFC_PFIFO_CLR) +X(CH0_FFC_RATE_MODE_RX) +X(CH1_FFC_RATE_MODE_RX) +X(CH0_FFC_RATE_MODE_TX) +X(CH1_FFC_RATE_MODE_TX) +X(CH0_FFC_DIV11_MODE_RX) +X(CH1_FFC_DIV11_MODE_RX) +X(CH0_FFC_RX_GEAR_MODE) +X(CH1_FFC_RX_GEAR_MODE) +X(CH0_FFC_TX_GEAR_MODE) +X(CH1_FFC_TX_GEAR_MODE) +X(CH0_FFC_DIV11_MODE_TX) +X(CH1_FFC_DIV11_MODE_TX) +X(CH0_FFC_LDR_CORE2TX_EN) +X(CH1_FFC_LDR_CORE2TX_EN) +X(CH0_FFC_LANE_TX_RST) +X(CH1_FFC_LANE_TX_RST) +X(CH0_FFC_LANE_RX_RST) +X(CH1_FFC_LANE_RX_RST) +X(CH0_FFC_RRST) +X(CH1_FFC_RRST) +X(CH0_FFC_TXPWDNB) +X(CH1_FFC_TXPWDNB) +X(CH0_FFC_RXPWDNB) +X(CH1_FFC_RXPWDNB) +X(CH0_LDR_CORE2TX) +X(CH1_LDR_CORE2TX) +X(D_SCIWDATA0) +X(D_SCIWDATA1) +X(D_SCIWDATA2) +X(D_SCIWDATA3) +X(D_SCIWDATA4) +X(D_SCIWDATA5) +X(D_SCIWDATA6) +X(D_SCIWDATA7) +X(D_SCIADDR0) +X(D_SCIADDR1) +X(D_SCIADDR2) +X(D_SCIADDR3) +X(D_SCIADDR4) +X(D_SCIADDR5) +X(D_SCIENAUX) +X(D_SCISELAUX) +X(CH0_SCIEN) +X(CH1_SCIEN) +X(CH0_SCISEL) +X(CH1_SCISEL) +X(D_SCIRD) +X(D_SCIWSTN) +X(D_CYAWSTN) +X(D_FFC_SYNC_TOGGLE) +X(D_FFC_DUAL_RST) +X(D_FFC_MACRO_RST) +X(D_FFC_MACROPDB) +X(D_FFC_TRST) +X(CH0_FFC_CDR_EN_BITSLIP) +X(CH1_FFC_CDR_EN_BITSLIP) +X(D_SCAN_ENABLE) +X(D_SCAN_IN_0) +X(D_SCAN_IN_1) +X(D_SCAN_IN_2) +X(D_SCAN_IN_3) +X(D_SCAN_IN_4) +X(D_SCAN_IN_5) +X(D_SCAN_IN_6) +X(D_SCAN_IN_7) +X(D_SCAN_MODE) +X(D_SCAN_RESET) +X(D_CIN0) +X(D_CIN1) +X(D_CIN2) +X(D_CIN3) +X(D_CIN4) +X(D_CIN5) +X(D_CIN6) +X(D_CIN7) +X(D_CIN8) +X(D_CIN9) +X(D_CIN10) +X(D_CIN11) +X(CH0_HDOUTP) +X(CH1_HDOUTP) +X(CH0_HDOUTN) +X(CH1_HDOUTN) +X(D_TXBIT_CLKP_TO_ND) +X(D_TXBIT_CLKN_TO_ND) +X(D_SYNC_PULSE2ND) +X(D_TXPLL_LOL_TO_ND) +X(CH0_FF_RX_F_CLK) +X(CH1_FF_RX_F_CLK) +X(CH0_FF_RX_H_CLK) +X(CH1_FF_RX_H_CLK) +X(CH0_FF_TX_F_CLK) +X(CH1_FF_TX_F_CLK) +X(CH0_FF_TX_H_CLK) +X(CH1_FF_TX_H_CLK) +X(CH0_FF_RX_PCLK) +X(CH1_FF_RX_PCLK) +X(CH0_FF_TX_PCLK) +X(CH1_FF_TX_PCLK) +X(CH0_FF_RX_D_0) +X(CH1_FF_RX_D_0) +X(CH0_FF_RX_D_1) +X(CH1_FF_RX_D_1) +X(CH0_FF_RX_D_2) +X(CH1_FF_RX_D_2) +X(CH0_FF_RX_D_3) +X(CH1_FF_RX_D_3) +X(CH0_FF_RX_D_4) +X(CH1_FF_RX_D_4) +X(CH0_FF_RX_D_5) +X(CH1_FF_RX_D_5) +X(CH0_FF_RX_D_6) +X(CH1_FF_RX_D_6) +X(CH0_FF_RX_D_7) +X(CH1_FF_RX_D_7) +X(CH0_FF_RX_D_8) +X(CH1_FF_RX_D_8) +X(CH0_FF_RX_D_9) +X(CH1_FF_RX_D_9) +X(CH0_FF_RX_D_10) +X(CH1_FF_RX_D_10) +X(CH0_FF_RX_D_11) +X(CH1_FF_RX_D_11) +X(CH0_FF_RX_D_12) +X(CH1_FF_RX_D_12) +X(CH0_FF_RX_D_13) +X(CH1_FF_RX_D_13) +X(CH0_FF_RX_D_14) +X(CH1_FF_RX_D_14) +X(CH0_FF_RX_D_15) +X(CH1_FF_RX_D_15) +X(CH0_FF_RX_D_16) +X(CH1_FF_RX_D_16) +X(CH0_FF_RX_D_17) +X(CH1_FF_RX_D_17) +X(CH0_FF_RX_D_18) +X(CH1_FF_RX_D_18) +X(CH0_FF_RX_D_19) +X(CH1_FF_RX_D_19) +X(CH0_FF_RX_D_20) +X(CH1_FF_RX_D_20) +X(CH0_FF_RX_D_21) +X(CH1_FF_RX_D_21) +X(CH0_FF_RX_D_22) +X(CH1_FF_RX_D_22) +X(CH0_FF_RX_D_23) +X(CH1_FF_RX_D_23) +X(CH0_FFS_PCIE_DONE) +X(CH1_FFS_PCIE_DONE) +X(CH0_FFS_PCIE_CON) +X(CH1_FFS_PCIE_CON) +X(CH0_FFS_RLOS) +X(CH1_FFS_RLOS) +X(CH0_FFS_LS_SYNC_STATUS) +X(CH1_FFS_LS_SYNC_STATUS) +X(CH0_FFS_CC_UNDERRUN) +X(CH1_FFS_CC_UNDERRUN) +X(CH0_FFS_CC_OVERRUN) +X(CH1_FFS_CC_OVERRUN) +X(CH0_FFS_RXFBFIFO_ERROR) +X(CH1_FFS_RXFBFIFO_ERROR) +X(CH0_FFS_TXFBFIFO_ERROR) +X(CH1_FFS_TXFBFIFO_ERROR) +X(CH0_FFS_RLOL) +X(CH1_FFS_RLOL) +X(CH0_FFS_SKP_ADDED) +X(CH1_FFS_SKP_ADDED) +X(CH0_FFS_SKP_DELETED) +X(CH1_FFS_SKP_DELETED) +X(CH0_LDR_RX2CORE) +X(CH1_LDR_RX2CORE) +X(D_SCIRDATA0) +X(D_SCIRDATA1) +X(D_SCIRDATA2) +X(D_SCIRDATA3) +X(D_SCIRDATA4) +X(D_SCIRDATA5) +X(D_SCIRDATA6) +X(D_SCIRDATA7) +X(D_SCIINT) +X(D_SCAN_OUT_0) +X(D_SCAN_OUT_1) +X(D_SCAN_OUT_2) +X(D_SCAN_OUT_3) +X(D_SCAN_OUT_4) +X(D_SCAN_OUT_5) +X(D_SCAN_OUT_6) +X(D_SCAN_OUT_7) +X(D_COUT0) +X(D_COUT1) +X(D_COUT2) +X(D_COUT3) +X(D_COUT4) +X(D_COUT5) +X(D_COUT6) +X(D_COUT7) +X(D_COUT8) +X(D_COUT9) +X(D_COUT10) +X(D_COUT11) +X(D_COUT12) +X(D_COUT13) +X(D_COUT14) +X(D_COUT15) +X(D_COUT16) +X(D_COUT17) +X(D_COUT18) +X(D_COUT19) +X(D_REFCLKI) +X(D_FFS_PLOL) + +X(PCSCLKDIV) +X(SEL2) +X(SEL1) +X(SEL0) +X(CDIV1) +X(CDIVX)
\ No newline at end of file diff --git a/ecp5/dcu_bitstream.h b/ecp5/dcu_bitstream.h new file mode 100644 index 00000000..0a5028d2 --- /dev/null +++ b/ecp5/dcu_bitstream.h @@ -0,0 +1,254 @@ +tg.config.add_word("DCU.CH0_AUTO_CALIB_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_AUTO_CALIB_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_AUTO_FACQ_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_AUTO_FACQ_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_BAND_THRESHOLD", parse_config_str(str_or_default(ci->params, ctx->id("CH0_BAND_THRESHOLD"), "0"), 6)); +tg.config.add_word("DCU.CH0_CALIB_CK_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_CALIB_CK_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_CC_MATCH_1", parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_1"), "0"), 10)); +tg.config.add_word("DCU.CH0_CC_MATCH_2", parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_2"), "0"), 10)); +tg.config.add_word("DCU.CH0_CC_MATCH_3", parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_3"), "0"), 10)); +tg.config.add_word("DCU.CH0_CC_MATCH_4", parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_4"), "0"), 10)); +tg.config.add_word("DCU.CH0_CDR_CNT4SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_CDR_CNT4SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_CDR_CNT8SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_CDR_CNT8SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_CTC_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_CTC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_DCOATDCFG", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOATDCFG"), "0"), 2)); +tg.config.add_word("DCU.CH0_DCOATDDLY", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOATDDLY"), "0"), 2)); +tg.config.add_word("DCU.CH0_DCOBYPSATD", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOBYPSATD"), "0"), 1)); +tg.config.add_word("DCU.CH0_DCOCALDIV", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOCALDIV"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOCTLGI", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOCTLGI"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCODISBDAVOID", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCODISBDAVOID"), "0"), 1)); +tg.config.add_word("DCU.CH0_DCOFLTDAC", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOFLTDAC"), "0"), 2)); +tg.config.add_word("DCU.CH0_DCOFTNRG", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOFTNRG"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOIOSTUNE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOIOSTUNE"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOITUNE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOITUNE"), "0"), 2)); +tg.config.add_word("DCU.CH0_DCOITUNE4LSB", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOITUNE4LSB"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOIUPDNX2", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOIUPDNX2"), "0"), 1)); +tg.config.add_word("DCU.CH0_DCONUOFLSB", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCONUOFLSB"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOSCALEI", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOSCALEI"), "0"), 2)); +tg.config.add_word("DCU.CH0_DCOSTARTVAL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOSTARTVAL"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOSTEP", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOSTEP"), "0"), 2)); +tg.config.add_word("DCU.CH0_DEC_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DEC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_ENABLE_CG_ALIGN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_ENABLE_CG_ALIGN"), "0"), 1)); +tg.config.add_word("DCU.CH0_ENC_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_ENC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_FF_RX_F_CLK_DIS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_RX_F_CLK_DIS"), "0"), 1)); +tg.config.add_word("DCU.CH0_FF_RX_H_CLK_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_RX_H_CLK_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_FF_TX_F_CLK_DIS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_TX_F_CLK_DIS"), "0"), 1)); +tg.config.add_word("DCU.CH0_FF_TX_H_CLK_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_TX_H_CLK_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_GE_AN_ENABLE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_GE_AN_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH0_INVERT_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_INVERT_RX"), "0"), 1)); +tg.config.add_word("DCU.CH0_INVERT_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_INVERT_TX"), "0"), 1)); +tg.config.add_word("DCU.CH0_LDR_CORE2TX_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_LDR_CORE2TX_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_LDR_RX2CORE_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_LDR_RX2CORE_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_LEQ_OFFSET_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_LEQ_OFFSET_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_LEQ_OFFSET_TRIM", parse_config_str(str_or_default(ci->params, ctx->id("CH0_LEQ_OFFSET_TRIM"), "0"), 3)); +tg.config.add_word("DCU.CH0_LSM_DISABLE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_LSM_DISABLE"), "0"), 1)); +tg.config.add_word("DCU.CH0_MATCH_2_ENABLE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_MATCH_2_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH0_MATCH_4_ENABLE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_MATCH_4_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH0_MIN_IPG_CNT", parse_config_str(str_or_default(ci->params, ctx->id("CH0_MIN_IPG_CNT"), "0"), 2)); +tg.config.add_word("DCU.CH0_PCIE_EI_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PCIE_EI_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_PCIE_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PCIE_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_PCS_DET_TIME_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PCS_DET_TIME_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_PDEN_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PDEN_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_PRBS_ENABLE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PRBS_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH0_PRBS_LOCK", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PRBS_LOCK"), "0"), 1)); +tg.config.add_word("DCU.CH0_PRBS_SELECTION", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PRBS_SELECTION"), "0"), 1)); +tg.config.add_word("DCU.CH0_RATE_MODE_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RATE_MODE_RX"), "0"), 1)); +tg.config.add_word("DCU.CH0_RATE_MODE_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RATE_MODE_TX"), "0"), 1)); +tg.config.add_word("DCU.CH0_RCV_DCC_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RCV_DCC_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_REG_BAND_OFFSET", parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_BAND_OFFSET"), "0"), 4)); +tg.config.add_word("DCU.CH0_REG_BAND_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_BAND_SEL"), "0"), 6)); +tg.config.add_word("DCU.CH0_REG_IDAC_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_IDAC_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_REG_IDAC_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_IDAC_SEL"), "0"), 10)); +tg.config.add_word("DCU.CH0_REQ_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_REQ_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_REQ_LVL_SET", parse_config_str(str_or_default(ci->params, ctx->id("CH0_REQ_LVL_SET"), "0"), 2)); +tg.config.add_word("DCU.CH0_RIO_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RIO_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_RLOS_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RLOS_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_RPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RPWDNB"), "0"), 1)); +tg.config.add_word("DCU.CH0_RTERM_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RTERM_RX"), "0"), 5)); +tg.config.add_word("DCU.CH0_RTERM_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RTERM_TX"), "0"), 5)); +tg.config.add_word("DCU.CH0_RXIN_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RXIN_CM"), "0"), 2)); +tg.config.add_word("DCU.CH0_RXTERM_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RXTERM_CM"), "0"), 2)); +tg.config.add_word("DCU.CH0_RX_DCO_CK_DIV", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_DCO_CK_DIV"), "0"), 3)); +tg.config.add_word("DCU.CH0_RX_DIV11_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_DIV11_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_RX_GEAR_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_GEAR_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_RX_GEAR_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_GEAR_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_RX_LOS_CEQ", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_CEQ"), "0"), 2)); +tg.config.add_word("DCU.CH0_RX_LOS_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_RX_LOS_HYST_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_HYST_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_RX_LOS_LVL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_LVL"), "0"), 3)); +tg.config.add_word("DCU.CH0_RX_RATE_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_RATE_SEL"), "0"), 4)); +tg.config.add_word("DCU.CH0_RX_SB_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_SB_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_SB_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_SB_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_SEL_SD_RX_CLK", parse_config_str(str_or_default(ci->params, ctx->id("CH0_SEL_SD_RX_CLK"), "0"), 1)); +tg.config.add_word("DCU.CH0_TDRV_DAT_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_DAT_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_POST_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_POST_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_TDRV_PRE_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_PRE_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_TDRV_SLICE0_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE0_CUR"), "0"), 3)); +tg.config.add_word("DCU.CH0_TDRV_SLICE0_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE0_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE1_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE1_CUR"), "0"), 3)); +tg.config.add_word("DCU.CH0_TDRV_SLICE1_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE1_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE2_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE2_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE2_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE2_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE3_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE3_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE3_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE3_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE4_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE4_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE4_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE4_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE5_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE5_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE5_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE5_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TPWDNB"), "0"), 1)); +tg.config.add_word("DCU.CH0_TX_CM_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_CM_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TX_DIV11_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_DIV11_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_TX_GEAR_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_GEAR_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_TX_GEAR_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_GEAR_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_TX_POST_SIGN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_POST_SIGN"), "0"), 1)); +tg.config.add_word("DCU.CH0_TX_PRE_SIGN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_PRE_SIGN"), "0"), 1)); +tg.config.add_word("DCU.CH0_UC_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_UC_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_UDF_COMMA_A", parse_config_str(str_or_default(ci->params, ctx->id("CH0_UDF_COMMA_A"), "0"), 10)); +tg.config.add_word("DCU.CH0_UDF_COMMA_B", parse_config_str(str_or_default(ci->params, ctx->id("CH0_UDF_COMMA_B"), "0"), 10)); +tg.config.add_word("DCU.CH0_UDF_COMMA_MASK", parse_config_str(str_or_default(ci->params, ctx->id("CH0_UDF_COMMA_MASK"), "0"), 10)); +tg.config.add_word("DCU.CH0_WA_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_WA_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_WA_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_WA_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_AUTO_CALIB_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_AUTO_CALIB_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_AUTO_FACQ_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_AUTO_FACQ_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_BAND_THRESHOLD", parse_config_str(str_or_default(ci->params, ctx->id("CH1_BAND_THRESHOLD"), "0"), 6)); +tg.config.add_word("DCU.CH1_CALIB_CK_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_CALIB_CK_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_CC_MATCH_1", parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_1"), "0"), 10)); +tg.config.add_word("DCU.CH1_CC_MATCH_2", parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_2"), "0"), 10)); +tg.config.add_word("DCU.CH1_CC_MATCH_3", parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_3"), "0"), 10)); +tg.config.add_word("DCU.CH1_CC_MATCH_4", parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_4"), "0"), 10)); +tg.config.add_word("DCU.CH1_CDR_CNT4SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_CDR_CNT4SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_CDR_CNT8SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_CDR_CNT8SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_CTC_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_CTC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_DCOATDCFG", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOATDCFG"), "0"), 2)); +tg.config.add_word("DCU.CH1_DCOATDDLY", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOATDDLY"), "0"), 2)); +tg.config.add_word("DCU.CH1_DCOBYPSATD", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOBYPSATD"), "0"), 1)); +tg.config.add_word("DCU.CH1_DCOCALDIV", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOCALDIV"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOCTLGI", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOCTLGI"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCODISBDAVOID", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCODISBDAVOID"), "0"), 1)); +tg.config.add_word("DCU.CH1_DCOFLTDAC", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOFLTDAC"), "0"), 2)); +tg.config.add_word("DCU.CH1_DCOFTNRG", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOFTNRG"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOIOSTUNE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOIOSTUNE"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOITUNE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOITUNE"), "0"), 2)); +tg.config.add_word("DCU.CH1_DCOITUNE4LSB", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOITUNE4LSB"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOIUPDNX2", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOIUPDNX2"), "0"), 1)); +tg.config.add_word("DCU.CH1_DCONUOFLSB", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCONUOFLSB"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOSCALEI", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOSCALEI"), "0"), 2)); +tg.config.add_word("DCU.CH1_DCOSTARTVAL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOSTARTVAL"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOSTEP", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOSTEP"), "0"), 2)); +tg.config.add_word("DCU.CH1_DEC_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DEC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_ENABLE_CG_ALIGN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_ENABLE_CG_ALIGN"), "0"), 1)); +tg.config.add_word("DCU.CH1_ENC_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_ENC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_FF_RX_F_CLK_DIS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_RX_F_CLK_DIS"), "0"), 1)); +tg.config.add_word("DCU.CH1_FF_RX_H_CLK_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_RX_H_CLK_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_FF_TX_F_CLK_DIS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_TX_F_CLK_DIS"), "0"), 1)); +tg.config.add_word("DCU.CH1_FF_TX_H_CLK_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_TX_H_CLK_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_GE_AN_ENABLE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_GE_AN_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH1_INVERT_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_INVERT_RX"), "0"), 1)); +tg.config.add_word("DCU.CH1_INVERT_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_INVERT_TX"), "0"), 1)); +tg.config.add_word("DCU.CH1_LDR_CORE2TX_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_LDR_CORE2TX_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_LDR_RX2CORE_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_LDR_RX2CORE_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_LEQ_OFFSET_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_LEQ_OFFSET_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_LEQ_OFFSET_TRIM", parse_config_str(str_or_default(ci->params, ctx->id("CH1_LEQ_OFFSET_TRIM"), "0"), 3)); +tg.config.add_word("DCU.CH1_LSM_DISABLE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_LSM_DISABLE"), "0"), 1)); +tg.config.add_word("DCU.CH1_MATCH_2_ENABLE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_MATCH_2_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH1_MATCH_4_ENABLE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_MATCH_4_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH1_MIN_IPG_CNT", parse_config_str(str_or_default(ci->params, ctx->id("CH1_MIN_IPG_CNT"), "0"), 2)); +tg.config.add_word("DCU.CH1_PCIE_EI_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PCIE_EI_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_PCIE_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PCIE_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_PCS_DET_TIME_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PCS_DET_TIME_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_PDEN_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PDEN_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_PRBS_ENABLE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PRBS_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH1_PRBS_LOCK", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PRBS_LOCK"), "0"), 1)); +tg.config.add_word("DCU.CH1_PRBS_SELECTION", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PRBS_SELECTION"), "0"), 1)); +tg.config.add_word("DCU.CH1_RATE_MODE_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RATE_MODE_RX"), "0"), 1)); +tg.config.add_word("DCU.CH1_RATE_MODE_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RATE_MODE_TX"), "0"), 1)); +tg.config.add_word("DCU.CH1_RCV_DCC_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RCV_DCC_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_REG_BAND_OFFSET", parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_BAND_OFFSET"), "0"), 4)); +tg.config.add_word("DCU.CH1_REG_BAND_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_BAND_SEL"), "0"), 6)); +tg.config.add_word("DCU.CH1_REG_IDAC_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_IDAC_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_REG_IDAC_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_IDAC_SEL"), "0"), 10)); +tg.config.add_word("DCU.CH1_REQ_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_REQ_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_REQ_LVL_SET", parse_config_str(str_or_default(ci->params, ctx->id("CH1_REQ_LVL_SET"), "0"), 2)); +tg.config.add_word("DCU.CH1_RIO_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RIO_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_RLOS_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RLOS_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_RPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RPWDNB"), "0"), 1)); +tg.config.add_word("DCU.CH1_RTERM_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RTERM_RX"), "0"), 5)); +tg.config.add_word("DCU.CH1_RTERM_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RTERM_TX"), "0"), 5)); +tg.config.add_word("DCU.CH1_RXIN_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RXIN_CM"), "0"), 2)); +tg.config.add_word("DCU.CH1_RXTERM_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RXTERM_CM"), "0"), 2)); +tg.config.add_word("DCU.CH1_RX_DCO_CK_DIV", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_DCO_CK_DIV"), "0"), 3)); +tg.config.add_word("DCU.CH1_RX_DIV11_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_DIV11_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_RX_GEAR_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_GEAR_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_RX_GEAR_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_GEAR_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_RX_LOS_CEQ", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_CEQ"), "0"), 2)); +tg.config.add_word("DCU.CH1_RX_LOS_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_RX_LOS_HYST_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_HYST_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_RX_LOS_LVL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_LVL"), "0"), 3)); +tg.config.add_word("DCU.CH1_RX_RATE_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_RATE_SEL"), "0"), 4)); +tg.config.add_word("DCU.CH1_RX_SB_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_SB_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_SB_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_SB_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_SEL_SD_RX_CLK", parse_config_str(str_or_default(ci->params, ctx->id("CH1_SEL_SD_RX_CLK"), "0"), 1)); +tg.config.add_word("DCU.CH1_TDRV_DAT_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_DAT_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_POST_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_POST_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_TDRV_PRE_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_PRE_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_TDRV_SLICE0_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE0_CUR"), "0"), 3)); +tg.config.add_word("DCU.CH1_TDRV_SLICE0_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE0_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE1_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE1_CUR"), "0"), 3)); +tg.config.add_word("DCU.CH1_TDRV_SLICE1_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE1_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE2_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE2_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE2_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE2_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE3_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE3_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE3_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE3_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE4_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE4_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE4_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE4_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE5_CUR", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE5_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE5_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE5_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TPWDNB"), "0"), 1)); +tg.config.add_word("DCU.CH1_TX_CM_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_CM_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TX_DIV11_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_DIV11_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_TX_GEAR_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_GEAR_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_TX_GEAR_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_GEAR_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_TX_POST_SIGN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_POST_SIGN"), "0"), 1)); +tg.config.add_word("DCU.CH1_TX_PRE_SIGN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_PRE_SIGN"), "0"), 1)); +tg.config.add_word("DCU.CH1_UC_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_UC_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_UDF_COMMA_A", parse_config_str(str_or_default(ci->params, ctx->id("CH1_UDF_COMMA_A"), "0"), 10)); +tg.config.add_word("DCU.CH1_UDF_COMMA_B", parse_config_str(str_or_default(ci->params, ctx->id("CH1_UDF_COMMA_B"), "0"), 10)); +tg.config.add_word("DCU.CH1_UDF_COMMA_MASK", parse_config_str(str_or_default(ci->params, ctx->id("CH1_UDF_COMMA_MASK"), "0"), 10)); +tg.config.add_word("DCU.CH1_WA_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_WA_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_WA_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_WA_MODE"), "0"), 1)); +tg.config.add_word("DCU.D_BITCLK_FROM_ND_EN", parse_config_str(str_or_default(ci->params, ctx->id("D_BITCLK_FROM_ND_EN"), "0"), 1)); +tg.config.add_word("DCU.D_BITCLK_LOCAL_EN", parse_config_str(str_or_default(ci->params, ctx->id("D_BITCLK_LOCAL_EN"), "0"), 1)); +tg.config.add_word("DCU.D_BITCLK_ND_EN", parse_config_str(str_or_default(ci->params, ctx->id("D_BITCLK_ND_EN"), "0"), 1)); +tg.config.add_word("DCU.D_BUS8BIT_SEL", parse_config_str(str_or_default(ci->params, ctx->id("D_BUS8BIT_SEL"), "0"), 1)); +tg.config.add_word("DCU.D_CDR_LOL_SET", parse_config_str(str_or_default(ci->params, ctx->id("D_CDR_LOL_SET"), "0"), 2)); +tg.config.add_word("DCU.D_CMUSETBIASI", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETBIASI"), "0"), 2)); +tg.config.add_word("DCU.D_CMUSETI4CPP", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETI4CPP"), "0"), 4)); +tg.config.add_word("DCU.D_CMUSETI4CPZ", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETI4CPZ"), "0"), 4)); +tg.config.add_word("DCU.D_CMUSETI4VCO", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETI4VCO"), "0"), 2)); +tg.config.add_word("DCU.D_CMUSETICP4P", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETICP4P"), "0"), 2)); +tg.config.add_word("DCU.D_CMUSETICP4Z", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETICP4Z"), "0"), 3)); +tg.config.add_word("DCU.D_CMUSETINITVCT", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETINITVCT"), "0"), 2)); +tg.config.add_word("DCU.D_CMUSETISCL4VCO", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETISCL4VCO"), "0"), 3)); +tg.config.add_word("DCU.D_CMUSETP1GM", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETP1GM"), "0"), 3)); +tg.config.add_word("DCU.D_CMUSETP2AGM", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETP2AGM"), "0"), 3)); +tg.config.add_word("DCU.D_CMUSETZGM", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETZGM"), "0"), 3)); +tg.config.add_word("DCU.D_DCO_CALIB_TIME_SEL", parse_config_str(str_or_default(ci->params, ctx->id("D_DCO_CALIB_TIME_SEL"), "0"), 2)); +tg.config.add_word("DCU.D_HIGH_MARK", parse_config_str(str_or_default(ci->params, ctx->id("D_HIGH_MARK"), "0"), 4)); +tg.config.add_word("DCU.D_IB_PWDNB", parse_config_str(str_or_default(ci->params, ctx->id("D_IB_PWDNB"), "0"), 1)); +tg.config.add_word("DCU.D_ISETLOS", parse_config_str(str_or_default(ci->params, ctx->id("D_ISETLOS"), "0"), 8)); +tg.config.add_word("DCU.D_LOW_MARK", parse_config_str(str_or_default(ci->params, ctx->id("D_LOW_MARK"), "0"), 4)); +tg.config.add_word("DCU.D_MACROPDB", parse_config_str(str_or_default(ci->params, ctx->id("D_MACROPDB"), "0"), 1)); +tg.config.add_word("DCU.D_PD_ISET", parse_config_str(str_or_default(ci->params, ctx->id("D_PD_ISET"), "0"), 2)); +tg.config.add_word("DCU.D_PLL_LOL_SET", parse_config_str(str_or_default(ci->params, ctx->id("D_PLL_LOL_SET"), "0"), 2)); +tg.config.add_word("DCU.D_REFCK_MODE", parse_config_str(str_or_default(ci->params, ctx->id("D_REFCK_MODE"), "0"), 3)); +tg.config.add_word("DCU.D_REQ_ISET", parse_config_str(str_or_default(ci->params, ctx->id("D_REQ_ISET"), "0"), 3)); +tg.config.add_word("DCU.D_RG_EN", parse_config_str(str_or_default(ci->params, ctx->id("D_RG_EN"), "0"), 1)); +tg.config.add_word("DCU.D_RG_SET", parse_config_str(str_or_default(ci->params, ctx->id("D_RG_SET"), "0"), 2)); +tg.config.add_word("DCU.D_SETICONST_AUX", parse_config_str(str_or_default(ci->params, ctx->id("D_SETICONST_AUX"), "0"), 2)); +tg.config.add_word("DCU.D_SETICONST_CH", parse_config_str(str_or_default(ci->params, ctx->id("D_SETICONST_CH"), "0"), 2)); +tg.config.add_word("DCU.D_SETIRPOLY_AUX", parse_config_str(str_or_default(ci->params, ctx->id("D_SETIRPOLY_AUX"), "0"), 2)); +tg.config.add_word("DCU.D_SETIRPOLY_CH", parse_config_str(str_or_default(ci->params, ctx->id("D_SETIRPOLY_CH"), "0"), 2)); +tg.config.add_word("DCU.D_SETPLLRC", parse_config_str(str_or_default(ci->params, ctx->id("D_SETPLLRC"), "0"), 6)); +tg.config.add_word("DCU.D_SYNC_LOCAL_EN", parse_config_str(str_or_default(ci->params, ctx->id("D_SYNC_LOCAL_EN"), "0"), 1)); +tg.config.add_word("DCU.D_SYNC_ND_EN", parse_config_str(str_or_default(ci->params, ctx->id("D_SYNC_ND_EN"), "0"), 1)); +tg.config.add_word("DCU.D_TXPLL_PWDNB", parse_config_str(str_or_default(ci->params, ctx->id("D_TXPLL_PWDNB"), "0"), 1)); +tg.config.add_word("DCU.D_TX_VCO_CK_DIV", parse_config_str(str_or_default(ci->params, ctx->id("D_TX_VCO_CK_DIV"), "0"), 3)); +tg.config.add_word("DCU.D_XGE_MODE", parse_config_str(str_or_default(ci->params, ctx->id("D_XGE_MODE"), "0"), 1)); diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 06412fef..66c62024 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -55,6 +55,9 @@ class Ecp5GlobalRouter { if (user.cell->type == id_TRELLIS_SLICE && (user.port == id_CLK || user.port == id_WCK)) return true; + if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK || + user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK)) + return true; return false; } @@ -65,8 +68,11 @@ class Ecp5GlobalRouter NetInfo *ni = net.second.get(); clockCount[ni->name] = 0; for (const auto &user : ni->users) { - if (is_clock_port(user)) + if (is_clock_port(user)) { clockCount[ni->name]++; + if (user.cell->type == id_DCUA) + clockCount[ni->name] += 100; + } } // log_info("clkcount %s: %d\n", ni->name.c_str(ctx),clockCount[ni->name]); } @@ -290,6 +296,10 @@ class Ecp5GlobalRouter float tns; return get_net_metric(ctx, clki, MetricType::WIRELENGTH, tns); } else { + // Check for dedicated routing + if (has_short_route(ctx->getBelPinWire(drv_bel, drv.port), ctx->getBelPinWire(dcc->bel, id_CLKI))) { + return 0; + } // Driver is locked Loc dcc_loc = ctx->getBelLocation(dcc->bel); Loc drv_loc = ctx->getBelLocation(drv_bel); @@ -297,6 +307,46 @@ class Ecp5GlobalRouter } } + // Return true if a short (<5) route exists between two wires + bool has_short_route(WireId src, WireId dst, int thresh = 5) + { + std::queue<WireId> visit; + std::unordered_map<WireId, PipId> backtrace; + visit.push(src); + WireId cursor; + while (true) { + + if (visit.empty() || visit.size() > 1000) { + // log_info ("dist %s -> %s = inf\n", ctx->getWireName(src).c_str(ctx), + // ctx->getWireName(dst).c_str(ctx)); + return false; + } + cursor = visit.front(); + visit.pop(); + + if (cursor == dst) + break; + for (auto dh : ctx->getPipsDownhill(cursor)) { + WireId pipDst = ctx->getPipDstWire(dh); + if (backtrace.count(pipDst)) + continue; + backtrace[pipDst] = dh; + visit.push(pipDst); + } + } + int length = 0; + while (true) { + auto fnd = backtrace.find(cursor); + if (fnd == backtrace.end()) + break; + cursor = ctx->getPipSrcWire(fnd->second); + length++; + } + // log_info ("dist %s -> %s = %d\n", ctx->getWireName(src).c_str(ctx), ctx->getWireName(dst).c_str(ctx), + // length); + return length < thresh; + } + // Attempt to place a DCC void place_dcc(CellInfo *dcc) { @@ -335,6 +385,8 @@ class Ecp5GlobalRouter for (auto user : net->users) { if (user.port == id_CLKFB) { keep_users.push_back(user); + } else if (net->driver.cell->type == id_EXTREFB && user.cell->type == id_DCUA) { + keep_users.push_back(user); } else { glbnet->users.push_back(user); user.cell->ports.at(user.port).net = glbnet.get(); @@ -350,6 +402,13 @@ class Ecp5GlobalRouter place_dcc(dcc.get()); + if (net->clkconstr) { + glbnet->clkconstr = std::unique_ptr<ClockConstraint>(new ClockConstraint()); + glbnet->clkconstr->low = net->clkconstr->low; + glbnet->clkconstr->high = net->clkconstr->high; + glbnet->clkconstr->period = net->clkconstr->period; + } + ctx->cells[dcc->name] = std::move(dcc); NetInfo *glbptr = glbnet.get(); ctx->nets[glbnet->name] = std::move(glbnet); diff --git a/ecp5/main.cc b/ecp5/main.cc index c444f96f..cc004df3 100644 --- a/ecp5/main.cc +++ b/ecp5/main.cc @@ -40,7 +40,7 @@ class ECP5CommandHandler : public CommandHandler void customBitstream(Context *ctx) override; protected: - po::options_description getArchOptions(); + po::options_description getArchOptions() override; }; ECP5CommandHandler::ECP5CommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {} diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 73e15609..2d2f7578 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -235,6 +235,46 @@ class Ecp5Packer } } + // Return true if an port is a top level port that provides its own IOBUF + bool is_top_port(PortRef &port) + { + if (port.cell == nullptr) + return false; + if (port.cell->type == id_DCUA) { + return port.port == id_CH0_HDINP || port.port == id_CH0_HDINN || port.port == id_CH0_HDOUTP || + port.port == id_CH0_HDOUTN || port.port == id_CH1_HDINP || port.port == id_CH1_HDINN || + port.port == id_CH1_HDOUTP || port.port == id_CH1_HDOUTN; + } else if (port.cell->type == id_EXTREFB) { + return port.port == id_REFCLKP || port.port == id_REFCLKN; + } else { + return false; + } + } + + // Return true if a net only drives a top port + bool drives_top_port(NetInfo *net, PortRef &tp) + { + if (net == nullptr) + return false; + for (auto user : net->users) { + if (is_top_port(user)) { + if (net->users.size() > 1) + log_error(" port %s.%s must be connected to (and only to) a top level pin\n", + user.cell->name.c_str(ctx), user.port.c_str(ctx)); + tp = user; + return true; + } + } + if (net->driver.cell != nullptr && is_top_port(net->driver)) { + if (net->users.size() > 1) + log_error(" port %s.%s must be connected to (and only to) a top level pin\n", + net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx)); + tp = net->driver; + return true; + } + return false; + } + // Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated void pack_io() { @@ -244,10 +284,14 @@ class Ecp5Packer CellInfo *ci = cell.second; if (is_nextpnr_iob(ctx, ci)) { CellInfo *trio = nullptr; + NetInfo *ionet = nullptr; + PortRef tp; if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) { - trio = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_trellis_io, ctx->id("B"), true, ci); + ionet = ci->ports.at(ctx->id("O")).net; + trio = net_only_drives(ctx, ionet, is_trellis_io, ctx->id("B"), true, ci); } else if (ci->type == ctx->id("$nextpnr_obuf")) { + ionet = ci->ports.at(ctx->id("I")).net; trio = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_trellis_io, ctx->id("B"), true, ci); } if (trio != nullptr) { @@ -266,6 +310,19 @@ class Ecp5Packer ctx->nets.erase(net2->name); } } + } else if (drives_top_port(ionet, tp)) { + log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx), + tp.cell->name.c_str(ctx), tp.port.c_str(ctx), ci->type.c_str(ctx), ci->name.c_str(ctx)); + if (ionet != nullptr) { + ctx->nets.erase(ionet->name); + tp.cell->ports.at(tp.port).net = nullptr; + } + if (ci->type == ctx->id("$nextpnr_iobuf")) { + NetInfo *net2 = ci->ports.at(ctx->id("I")).net; + if (net2 != nullptr) { + ctx->nets.erase(net2->name); + } + } } else { // Create a TRELLIS_IO buffer std::unique_ptr<CellInfo> tr_cell = @@ -276,21 +333,23 @@ class Ecp5Packer } packed_cells.insert(ci->name); - for (const auto &attr : ci->attrs) - trio->attrs[attr.first] = attr.second; - - auto loc_attr = trio->attrs.find(ctx->id("LOC")); - if (loc_attr != trio->attrs.end()) { - std::string pin = loc_attr->second; - BelId pinBel = ctx->getPackagePinBel(pin); - if (pinBel == BelId()) { - log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n", - trio->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str()); - } else { - log_info("pin '%s' constrained to Bel '%s'.\n", trio->name.c_str(ctx), - ctx->getBelName(pinBel).c_str(ctx)); + if (trio != nullptr) { + for (const auto &attr : ci->attrs) + trio->attrs[attr.first] = attr.second; + + auto loc_attr = trio->attrs.find(ctx->id("LOC")); + if (loc_attr != trio->attrs.end()) { + std::string pin = loc_attr->second; + BelId pinBel = ctx->getPackagePinBel(pin); + if (pinBel == BelId()) { + log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n", + trio->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str()); + } else { + log_info("pin '%s' constrained to Bel '%s'.\n", trio->name.c_str(ctx), + ctx->getBelName(pinBel).c_str(ctx)); + } + trio->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx); } - trio->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx); } } } @@ -1033,12 +1092,93 @@ class Ecp5Packer } } + // "Pack" DCUs + void pack_dcus() + { + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_DCUA) { + if (ci->attrs.count(ctx->id("LOC"))) { + std::string loc = ci->attrs.at(ctx->id("LOC")); + if (loc == "DCU0" && + (ctx->args.type == ArchArgs::LFE5UM_25F || ctx->args.type == ArchArgs::LFE5UM5G_25F)) + ci->attrs[ctx->id("BEL")] = "X42/Y50/DCU"; + else if (loc == "DCU0" && + (ctx->args.type == ArchArgs::LFE5UM_45F || ctx->args.type == ArchArgs::LFE5UM5G_45F)) + ci->attrs[ctx->id("BEL")] = "X42/Y71/DCU"; + else if (loc == "DCU1" && + (ctx->args.type == ArchArgs::LFE5UM_45F || ctx->args.type == ArchArgs::LFE5UM5G_45F)) + ci->attrs[ctx->id("BEL")] = "X69/Y71/DCU"; + else if (loc == "DCU0" && + (ctx->args.type == ArchArgs::LFE5UM_85F || ctx->args.type == ArchArgs::LFE5UM5G_85F)) + ci->attrs[ctx->id("BEL")] = "X46/Y95/DCU"; + else if (loc == "DCU1" && + (ctx->args.type == ArchArgs::LFE5UM_85F || ctx->args.type == ArchArgs::LFE5UM5G_85F)) + ci->attrs[ctx->id("BEL")] = "X71/Y95/DCU"; + else + log_error("no DCU location '%s' in device '%s'\n", loc.c_str(), ctx->getChipName().c_str()); + } + if (!ci->attrs.count(ctx->id("BEL"))) + log_error("DCU must be constrained to a Bel!\n"); + // Empty port auto-creation to generate correct tie-downs + BelId exemplar_bel; + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) == id_DCUA) { + exemplar_bel = bel; + break; + } + } + NPNR_ASSERT(exemplar_bel != BelId()); + for (auto pin : ctx->getBelPins(exemplar_bel)) + if (ctx->getBelPinType(exemplar_bel, pin) == PORT_IN) + autocreate_empty_port(ci, pin); + } + } + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_EXTREFB) { + const NetInfo *refo = net_or_nullptr(ci, id_REFCLKO); + CellInfo *dcu = nullptr; + if (refo == nullptr) + log_error("EXTREFB REFCLKO must not be unconnected\n"); + for (auto user : refo->users) { + if (user.cell->type != id_DCUA) + continue; + if (dcu != nullptr && dcu != user.cell) + log_error("EXTREFB REFCLKO must only drive a single DCUA\n"); + dcu = user.cell; + } + if (!dcu->attrs.count(ctx->id("BEL"))) + log_error("DCU must be constrained to a Bel!\n"); + std::string bel = dcu->attrs.at(ctx->id("BEL")); + NPNR_ASSERT(bel.substr(bel.length() - 3) == "DCU"); + bel.replace(bel.length() - 3, 3, "EXTREF"); + ci->attrs[ctx->id("BEL")] = bel; + } else if (ci->type == id_PCSCLKDIV) { + const NetInfo *clki = net_or_nullptr(ci, id_CLKI); + if (clki != nullptr && clki->driver.cell != nullptr && clki->driver.cell->type == id_DCUA) { + CellInfo *dcu = clki->driver.cell; + if (!dcu->attrs.count(ctx->id("BEL"))) + log_error("DCU must be constrained to a Bel!\n"); + BelId bel = ctx->getBelByName(ctx->id(dcu->attrs.at(ctx->id("BEL")))); + if (bel == BelId()) + log_error("Invalid DCU bel '%s'\n", dcu->attrs.at(ctx->id("BEL")).c_str()); + Loc loc = ctx->getBelLocation(bel); + // DCU0 -> CLKDIV z=0; DCU1 -> CLKDIV z=1 + ci->constr_abs_z = true; + ci->constr_z = (loc.x >= 69) ? 1 : 0; + } + } + } + } + public: void pack() { pack_io(); pack_ebr(); pack_dsps(); + pack_dcus(); pack_constants(); pack_dram(); pack_carries(); diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py index 9a26b605..99fe7ba9 100755 --- a/ecp5/trellis_import.py +++ b/ecp5/trellis_import.py @@ -200,9 +200,14 @@ def write_database(dev_name, chip, ddrg, endianness): write_loc(arc.sinkWire.rel, "dst") bba.u32(arc.srcWire.id, "src_idx") bba.u32(arc.sinkWire.id, "dst_idx") - bba.u32(get_pip_delay(get_wire_name(idx, arc.srcWire.rel, arc.srcWire.id), get_wire_name(idx, arc.sinkWire.rel, arc.sinkWire.id)), "delay") # TODO:delay + src_name = get_wire_name(idx, arc.srcWire.rel, arc.srcWire.id) + snk_name = get_wire_name(idx, arc.sinkWire.rel, arc.sinkWire.id) + bba.u32(get_pip_delay(src_name, snk_name), "delay") # TODO:delay bba.u16(get_tiletype_index(ddrg.to_str(arc.tiletype)), "tile_type") - bba.u8(int(arc.cls), "pip_type") + cls = arc.cls + if cls == 1 and "PCS" in snk_name or "DCU" in snk_name or "DCU" in src_name: + cls = 2 + bba.u8(cls, "pip_type") bba.u8(0, "padding") if len(loctype.wires) > 0: for wire_idx in range(len(loctype.wires)): @@ -340,7 +345,7 @@ def write_database(dev_name, chip, ddrg, endianness): bba.pop() return bba -dev_names = {"25k": "LFE5U-25F", "45k": "LFE5U-45F", "85k": "LFE5U-85F"} +dev_names = {"25k": "LFE5UM5G-25F", "45k": "LFE5UM5G-45F", "85k": "LFE5UM5G-85F"} def main(): global max_row, max_col diff --git a/generic/arch.cc b/generic/arch.cc index 3e95159a..77417d27 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -373,6 +373,8 @@ NetInfo *Arch::getBoundPipNet(PipId pip) const { return pips.at(pip).bound_net; NetInfo *Arch::getConflictingPipNet(PipId pip) const { return pips.at(pip).bound_net; } +WireId Arch::getConflictingPipWire(PipId pip) const { return pips.at(pip).bound_net ? pips.at(pip).dstWire : WireId(); } + const std::vector<PipId> &Arch::getPips() const { return pip_ids; } Loc Arch::getPipLocation(PipId pip) const { return pips.at(pip).loc; } @@ -461,11 +463,16 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort } // Get the port class, also setting clockPort if applicable -TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const +TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const { return TMG_IGNORE; } +TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const +{ + NPNR_ASSERT_FALSE("no clocking info for generic"); +} + bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; } bool Arch::isBelLocationValid(BelId bel) const { return true; } diff --git a/generic/arch.h b/generic/arch.h index 22966e2a..dc4258cc 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -172,6 +172,7 @@ struct Arch : BaseCtx void unbindWire(WireId wire); bool checkWireAvail(WireId wire) const; NetInfo *getBoundWireNet(WireId wire) const; + WireId getConflictingWireWire(WireId wire) const { return wire; } NetInfo *getConflictingWireNet(WireId wire) const; DelayInfo getWireDelay(WireId wire) const { return DelayInfo(); } const std::vector<WireId> &getWires() const; @@ -186,6 +187,7 @@ struct Arch : BaseCtx void unbindPip(PipId pip); bool checkPipAvail(PipId pip) const; NetInfo *getBoundPipNet(PipId pip) const; + WireId getConflictingPipWire(PipId pip) const; NetInfo *getConflictingPipNet(PipId pip) const; const std::vector<PipId> &getPips() const; Loc getPipLocation(PipId pip) const; @@ -209,6 +211,14 @@ struct Arch : BaseCtx delay_t getDelayEpsilon() const { return 0.01; } delay_t getRipupDelayPenalty() const { return 1.0; } float getDelayNS(delay_t v) const { return v; } + + DelayInfo getDelayFromNS(float ns) const + { + DelayInfo del; + del.delay = ns; + return del; + } + uint32_t getDelayChecksum(delay_t v) const { return 0; } bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; @@ -223,8 +233,10 @@ struct Arch : BaseCtx DecalXY getGroupDecal(GroupId group) const; bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; - // Get the port class, also setting clockPort if applicable - TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const; + // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port + TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const; + // Get the TimingClockingInfo of a port + TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const; bool isValidBelForCell(CellInfo *cell, BelId bel) const; bool isBelLocationValid(BelId bel) const; diff --git a/generic/main.cc b/generic/main.cc index 412a28ac..08b0b348 100644 --- a/generic/main.cc +++ b/generic/main.cc @@ -37,7 +37,7 @@ class GenericCommandHandler : public CommandHandler void customBitstream(Context *ctx) override; protected: - po::options_description getArchOptions(); + po::options_description getArchOptions() override; }; GenericCommandHandler::GenericCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {} diff --git a/gui/create_img.sh b/gui/create_img.sh new file mode 100755 index 00000000..1508d023 --- /dev/null +++ b/gui/create_img.sh @@ -0,0 +1,6 @@ +convert -font helvetica -fill red -pointsize 8 -gravity center -draw "text 2,8 'JSON'" resources/open.png resources/open_json.png +convert -font helvetica -fill red -pointsize 8 -gravity center -draw "text 2,8 'PCF'" resources/open.png ice40/resources/open_pcf.png +convert -font helvetica -fill red -pointsize 8 -gravity center -draw "text 2,8 'BASE'" resources/open.png ecp5/resources/open_base.png +convert -font helvetica -fill red -pointsize 8 -gravity center -draw "text 2,8 'LPF'" resources/open.png ecp5/resources/open_lpf.png +convert -font helvetica -fill red -pointsize 8 -gravity center -draw "text 2,8 'ASC'" resources/save.png ice40/resources/save_asc.png +convert -font helvetica -fill red -pointsize 7 -gravity center -draw "text 2,8 'CONFIG'" resources/save.png ecp5/resources/save_config.png
\ No newline at end of file diff --git a/gui/designwidget.cc b/gui/designwidget.cc index a45752fc..9895cad1 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -20,6 +20,7 @@ #include "designwidget.h"
#include <QAction>
+#include <QApplication>
#include <QGridLayout>
#include <QLineEdit>
#include <QMenu>
@@ -46,15 +47,28 @@ void TreeView::mouseMoveEvent(QMouseEvent *event) void TreeView::leaveEvent(QEvent *event) { Q_EMIT hoverIndexChanged(QModelIndex()); }
-DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), selectionModel(nullptr)
+DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr)
{
+ tabWidget = new QTabWidget();
+
// Add tree view
- treeView = new TreeView();
- treeModel = new TreeModel::Model();
- treeView->setModel(treeModel);
- treeView->setContextMenuPolicy(Qt::CustomContextMenu);
- treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
- treeView->viewport()->setMouseTracking(true);
+ for (int i = 0; i < 6; i++) {
+ treeView[i] = new TreeView();
+ treeModel[i] = new TreeModel::Model();
+ treeView[i]->setModel(treeModel[i]);
+ treeView[i]->setContextMenuPolicy(Qt::CustomContextMenu);
+ treeView[i]->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ treeView[i]->viewport()->setMouseTracking(true);
+ selectionModel[i] = nullptr;
+ }
+
+ tabWidget->addTab(treeView[0], "Bels");
+ tabWidget->addTab(treeView[1], "Wires");
+ tabWidget->addTab(treeView[2], "Pips");
+ tabWidget->addTab(treeView[3], "Cells");
+ tabWidget->addTab(treeView[4], "Nets");
+ tabWidget->addTab(treeView[5], "Groups");
+
// Add property view
variantManager = new QtVariantPropertyManager(this);
readOnlyManager = new QtVariantPropertyManager(this);
@@ -80,7 +94,14 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel connect(actionFirst, &QAction::triggered, this, [this] {
history_ignore = true;
history_index = 0;
- selectionModel->setCurrentIndex(history.at(history_index), QItemSelectionModel::ClearAndSelect);
+ auto h = history.at(history_index);
+ if (tabWidget->currentIndex() != h.first) {
+ selectionModel[tabWidget->currentIndex()]->clearSelection();
+ tabWidget->setCurrentIndex(h.first);
+ selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select);
+ } else {
+ selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect);
+ }
updateButtons();
});
@@ -90,7 +111,14 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel connect(actionPrev, &QAction::triggered, this, [this] {
history_ignore = true;
history_index--;
- selectionModel->setCurrentIndex(history.at(history_index), QItemSelectionModel::ClearAndSelect);
+ auto h = history.at(history_index);
+ if (tabWidget->currentIndex() != h.first) {
+ selectionModel[tabWidget->currentIndex()]->clearSelection();
+ tabWidget->setCurrentIndex(h.first);
+ selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select);
+ } else {
+ selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect);
+ }
updateButtons();
});
@@ -100,7 +128,14 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel connect(actionNext, &QAction::triggered, this, [this] {
history_ignore = true;
history_index++;
- selectionModel->setCurrentIndex(history.at(history_index), QItemSelectionModel::ClearAndSelect);
+ auto h = history.at(history_index);
+ if (tabWidget->currentIndex() != h.first) {
+ selectionModel[tabWidget->currentIndex()]->clearSelection();
+ tabWidget->setCurrentIndex(h.first);
+ selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select);
+ } else {
+ selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect);
+ }
updateButtons();
});
@@ -110,7 +145,14 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel connect(actionLast, &QAction::triggered, this, [this] {
history_ignore = true;
history_index = int(history.size() - 1);
- selectionModel->setCurrentIndex(history.at(history_index), QItemSelectionModel::ClearAndSelect);
+ auto h = history.at(history_index);
+ if (tabWidget->currentIndex() != h.first) {
+ selectionModel[tabWidget->currentIndex()]->clearSelection();
+ tabWidget->setCurrentIndex(h.first);
+ selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select);
+ } else {
+ selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect);
+ }
updateButtons();
});
@@ -120,11 +162,14 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel connect(actionClear, &QAction::triggered, this, [this] {
history_index = -1;
history.clear();
- QModelIndex index = selectionModel->selectedIndexes().at(0);
- if (index.isValid()) {
- ElementType type = treeModel->nodeFromIndex(index)->type();
- if (type != ElementType::NONE)
- addToHistory(index);
+ int num = tabWidget->currentIndex();
+ if (selectionModel[num]->selectedIndexes().size() > 0) {
+ QModelIndex index = selectionModel[num]->selectedIndexes().at(0);
+ if (index.isValid()) {
+ ElementType type = treeModel[num]->nodeFromIndex(index)->type();
+ if (type != ElementType::NONE)
+ addToHistory(num, index);
+ }
}
updateButtons();
});
@@ -142,7 +187,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel vbox1->setSpacing(5);
vbox1->setContentsMargins(0, 0, 0, 0);
vbox1->addWidget(searchEdit);
- vbox1->addWidget(treeView);
+ vbox1->addWidget(tabWidget);
QWidget *toolbarWidget = new QWidget();
QHBoxLayout *hbox = new QHBoxLayout;
@@ -177,11 +222,18 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel connect(propertyEditor->treeWidget(), &QTreeWidget::itemDoubleClicked, this, &DesignWidget::onItemDoubleClicked);
connect(propertyEditor, &QtTreePropertyBrowser::hoverPropertyChanged, this, &DesignWidget::onHoverPropertyChanged);
- connect(treeView, &TreeView::customContextMenuRequested, this, &DesignWidget::prepareMenuTree);
- connect(treeView, &TreeView::doubleClicked, this, &DesignWidget::onDoubleClicked);
- connect(treeView, &TreeView::hoverIndexChanged, this, &DesignWidget::onHoverIndexChanged);
- selectionModel = treeView->selectionModel();
- connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &DesignWidget::onSelectionChanged);
+ for (int num = 0; num < 6; num++) {
+ connect(treeView[num], &TreeView::customContextMenuRequested,
+ [this, num](const QPoint &pos) { prepareMenuTree(num, pos); });
+ connect(treeView[num], &TreeView::doubleClicked, [this](const QModelIndex &index) { onDoubleClicked(index); });
+ connect(treeView[num], &TreeView::hoverIndexChanged,
+ [this, num](QModelIndex index) { onHoverIndexChanged(num, index); });
+ selectionModel[num] = treeView[num]->selectionModel();
+ connect(selectionModel[num], &QItemSelectionModel::selectionChanged,
+ [this, num](const QItemSelection &selected, const QItemSelection &deselected) {
+ onSelectionChanged(num, selected, deselected);
+ });
+ }
history_index = -1;
history_ignore = false;
@@ -207,13 +259,13 @@ void DesignWidget::updateButtons() actionLast->setEnabled(history_index < (count - 1));
}
-void DesignWidget::addToHistory(QModelIndex item)
+void DesignWidget::addToHistory(int tab, QModelIndex item)
{
if (!history_ignore) {
int count = int(history.size());
for (int i = count - 1; i > history_index; i--)
history.pop_back();
- history.push_back(item);
+ history.push_back(std::make_pair(tab, item));
history_index++;
}
history_ignore = false;
@@ -236,7 +288,58 @@ void DesignWidget::newContext(Context *ctx) {
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
- treeModel->loadContext(ctx);
+
+ {
+ TreeModel::ElementXYRoot<BelId>::ElementMap belMap;
+ for (const auto& bel : ctx->getBels()) {
+ auto loc = ctx->getBelLocation(bel);
+ belMap[std::pair<int, int>(loc.x, loc.y)].push_back(bel);
+ }
+ auto belGetter = [](Context *ctx, BelId id) { return ctx->getBelName(id); };
+
+ getTreeByElementType(ElementType::BEL)
+ ->loadData(ctx, std::unique_ptr<TreeModel::ElementXYRoot<BelId>>(
+ new TreeModel::ElementXYRoot<BelId>(ctx, belMap, belGetter, ElementType::BEL)));
+ }
+
+ {
+ TreeModel::ElementXYRoot<WireId>::ElementMap wireMap;
+#ifdef ARCH_ICE40
+ for (int i = 0; i < ctx->chip_info->num_wires; i++) {
+ const auto wire = &ctx->chip_info->wire_data[i];
+ WireId wireid;
+ wireid.index = i;
+ wireMap[std::pair<int, int>(wire->x, wire->y)].push_back(wireid);
+ }
+#endif
+#ifdef ARCH_ECP5
+ for (const auto& wire : ctx->getWires()) {
+ wireMap[std::pair<int, int>(wire.location.x, wire.location.y)].push_back(wire);
+ }
+#endif
+ auto wireGetter = [](Context *ctx, WireId id) { return ctx->getWireName(id); };
+ getTreeByElementType(ElementType::WIRE)
+ ->loadData(ctx, std::unique_ptr<TreeModel::ElementXYRoot<WireId>>(
+ new TreeModel::ElementXYRoot<WireId>(ctx, wireMap, wireGetter, ElementType::WIRE)));
+ }
+
+ {
+ TreeModel::ElementXYRoot<PipId>::ElementMap pipMap;
+ for (const auto& pip : ctx->getPips()) {
+ auto loc = ctx->getPipLocation(pip);
+ pipMap[std::pair<int, int>(loc.x, loc.y)].push_back(pip);
+ }
+ auto pipGetter = [](Context *ctx, PipId id) { return ctx->getPipName(id); };
+
+ getTreeByElementType(ElementType::PIP)
+ ->loadData(ctx, std::unique_ptr<TreeModel::ElementXYRoot<PipId>>(
+ new TreeModel::ElementXYRoot<PipId>(ctx, pipMap, pipGetter, ElementType::PIP)));
+ }
+
+ getTreeByElementType(ElementType::CELL)
+ ->loadData(ctx, std::unique_ptr<TreeModel::IdStringList>(new TreeModel::IdStringList(ElementType::CELL)));
+ getTreeByElementType(ElementType::NET)
+ ->loadData(ctx, std::unique_ptr<TreeModel::IdStringList>(new TreeModel::IdStringList(ElementType::NET)));
}
updateTree();
}
@@ -260,7 +363,18 @@ void DesignWidget::updateTree() {
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
- treeModel->updateCellsNets(ctx);
+
+ std::vector<IdString> cells;
+ for (auto &pair : ctx->cells) {
+ cells.push_back(pair.first);
+ }
+ std::vector<IdString> nets;
+ for (auto &pair : ctx->nets) {
+ nets.push_back(pair.first);
+ }
+
+ getTreeByElementType(ElementType::CELL)->updateElements(cells);
+ getTreeByElementType(ElementType::NET)->updateElements(nets);
}
}
QtProperty *DesignWidget::addTopLevelProperty(const QString &id)
@@ -315,6 +429,40 @@ ElementType DesignWidget::getElementTypeByName(QString type) return ElementType::NONE;
}
+TreeModel::Model *DesignWidget::getTreeByElementType(ElementType type)
+{
+ if (type == ElementType::NONE)
+ return nullptr;
+ if (type == ElementType::BEL)
+ return treeModel[0];
+ if (type == ElementType::WIRE)
+ return treeModel[1];
+ if (type == ElementType::PIP)
+ return treeModel[2];
+ if (type == ElementType::NET)
+ return treeModel[3];
+ if (type == ElementType::CELL)
+ return treeModel[4];
+ return nullptr;
+}
+int DesignWidget::getIndexByElementType(ElementType type)
+{
+ if (type == ElementType::NONE)
+ return -1;
+ if (type == ElementType::BEL)
+ return 0;
+ if (type == ElementType::WIRE)
+ return 1;
+ if (type == ElementType::PIP)
+ return 2;
+ if (type == ElementType::NET)
+ return 3;
+ if (type == ElementType::CELL)
+ return 4;
+ if (type == ElementType::GROUP)
+ return 5;
+ return -1;
+}
void DesignWidget::addProperty(QtProperty *topItem, int propertyType, const QString &name, QVariant value,
const ElementType &type)
{
@@ -333,6 +481,12 @@ QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name) return item;
}
+void DesignWidget::clearAllSelectionModels()
+{
+ for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++)
+ selectionModel[i]->clearSelection();
+}
+
void DesignWidget::onClickedBel(BelId bel, bool keep)
{
boost::optional<TreeModel::Item *> item;
@@ -340,14 +494,20 @@ void DesignWidget::onClickedBel(BelId bel, bool keep) std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
- item = treeModel->nodeForIdType(ElementType::BEL, ctx->getBelName(bel));
+ item = getTreeByElementType(ElementType::BEL)->nodeForId(ctx->getBelName(bel));
if (!item)
return;
Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep);
}
- selectionModel->setCurrentIndex(treeModel->indexFromNode(*item),
- keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
+ int index = getIndexByElementType(ElementType::BEL);
+ if (!keep)
+ clearAllSelectionModels();
+ if (tabWidget->currentIndex() != index) {
+ tabWidget->setCurrentIndex(index);
+ }
+ selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::BEL)->indexFromNode(*item),
+ keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
}
void DesignWidget::onClickedWire(WireId wire, bool keep)
@@ -357,14 +517,19 @@ void DesignWidget::onClickedWire(WireId wire, bool keep) std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
- item = treeModel->nodeForIdType(ElementType::WIRE, ctx->getWireName(wire));
+ item = getTreeByElementType(ElementType::WIRE)->nodeForId(ctx->getWireName(wire));
if (!item)
return;
Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep);
}
- selectionModel->setCurrentIndex(treeModel->indexFromNode(*item),
- keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
+ int index = getIndexByElementType(ElementType::WIRE);
+ if (!keep)
+ clearAllSelectionModels();
+ if (tabWidget->currentIndex() != index)
+ tabWidget->setCurrentIndex(index);
+ selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::WIRE)->indexFromNode(*item),
+ keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
}
void DesignWidget::onClickedPip(PipId pip, bool keep)
@@ -374,41 +539,61 @@ void DesignWidget::onClickedPip(PipId pip, bool keep) std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
- item = treeModel->nodeForIdType(ElementType::PIP, ctx->getPipName(pip));
+ item = getTreeByElementType(ElementType::PIP)->nodeForId(ctx->getPipName(pip));
if (!item)
return;
Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep);
}
- selectionModel->setCurrentIndex(treeModel->indexFromNode(*item),
- keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
+
+ int index = getIndexByElementType(ElementType::PIP);
+ if (!keep)
+ clearAllSelectionModels();
+ if (tabWidget->currentIndex() != index)
+ tabWidget->setCurrentIndex(index);
+ selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::PIP)->indexFromNode(*item),
+ keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
}
-void DesignWidget::onSelectionChanged(const QItemSelection &, const QItemSelection &)
+void DesignWidget::onSelectionChanged(int num, const QItemSelection &, const QItemSelection &)
{
- if (selectionModel->selectedIndexes().size() == 0)
- return;
-
- if (selectionModel->selectedIndexes().size() > 1) {
- std::vector<DecalXY> decals;
- for (auto index : selectionModel->selectedIndexes()) {
- TreeModel::Item *item = treeModel->nodeFromIndex(index);
+ int num_selected = 0;
+ std::vector<DecalXY> decals;
+ for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) {
+ num_selected += selectionModel[i]->selectedIndexes().size();
+ for (auto index : selectionModel[i]->selectedIndexes()) {
+ TreeModel::Item *item = treeModel[i]->nodeFromIndex(index);
std::vector<DecalXY> d = getDecals(item->type(), item->id());
std::move(d.begin(), d.end(), std::back_inserter(decals));
}
+ }
+
+ // Keep other tree seleciton only if Control is pressed
+ if (num_selected > 1 && QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) == true) {
Q_EMIT selected(decals, false);
return;
}
- QModelIndex index = selectionModel->selectedIndexes().at(0);
+
+ // For deselect and multiple select just send all
+ if (selectionModel[num]->selectedIndexes().size() != 1) {
+ Q_EMIT selected(decals, false);
+ return;
+ }
+
+ QModelIndex index = selectionModel[num]->selectedIndexes().at(0);
if (!index.isValid())
return;
- TreeModel::Item *clickItem = treeModel->nodeFromIndex(index);
+ TreeModel::Item *clickItem = treeModel[num]->nodeFromIndex(index);
ElementType type = clickItem->type();
if (type == ElementType::NONE)
return;
- addToHistory(index);
+ // Clear other tab selections
+ for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++)
+ if (i!=num) selectionModel[i]->clearSelection();
+
+ addToHistory(num, index);
clearProperties();
@@ -453,6 +638,8 @@ void DesignWidget::onSelectionChanged(const QItemSelection &, const QItemSelecti addProperty(topItem, QVariant::String, "Type", ctx->getWireType(wire).c_str(ctx));
addProperty(topItem, QVariant::Bool, "Available", ctx->checkWireAvail(wire));
addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundWireNet(wire)), ElementType::NET);
+ addProperty(topItem, QVariant::String, "Conflicting Wire",
+ ctx->getWireName(ctx->getConflictingWireWire(wire)).c_str(ctx), ElementType::WIRE);
addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingWireNet(wire)),
ElementType::NET);
@@ -513,6 +700,8 @@ void DesignWidget::onSelectionChanged(const QItemSelection &, const QItemSelecti addProperty(topItem, QVariant::String, "Type", ctx->getPipType(pip).c_str(ctx));
addProperty(topItem, QVariant::Bool, "Available", ctx->checkPipAvail(pip));
addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundPipNet(pip)), ElementType::NET);
+ addProperty(topItem, QVariant::String, "Conflicting Wire",
+ ctx->getWireName(ctx->getConflictingPipWire(pip)).c_str(ctx), ElementType::WIRE);
addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingPipNet(pip)),
ElementType::NET);
addProperty(topItem, QVariant::String, "Src Wire", ctx->getWireName(ctx->getPipSrcWire(pip)).c_str(ctx),
@@ -714,7 +903,7 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos) if (type == ElementType::NONE)
continue;
IdString value = ctx->id(selectedProperty->valueText().toStdString());
- auto node = treeModel->nodeForIdType(type, value);
+ auto node = getTreeByElementType(type)->nodeForId(value);
if (!node)
continue;
items.append(*node);
@@ -755,17 +944,19 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos) menu.exec(tree->mapToGlobal(pos));
}
-void DesignWidget::prepareMenuTree(const QPoint &pos)
+void DesignWidget::prepareMenuTree(int num, const QPoint &pos)
{
int selectedIndex = -1;
- if (selectionModel->selectedIndexes().size() == 0)
+ if (selectionModel[num]->selectedIndexes().size() == 0)
return;
QList<TreeModel::Item *> items;
- for (auto index : selectionModel->selectedIndexes()) {
- TreeModel::Item *item = treeModel->nodeFromIndex(index);
- items.append(item);
+ for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) {
+ for (auto index : selectionModel[i]->selectedIndexes()) {
+ TreeModel::Item *item = treeModel[i]->nodeFromIndex(index);
+ items.append(item);
+ }
}
if (items.size() == 1) {
TreeModel::Item *item = items.at(0);
@@ -787,23 +978,31 @@ void DesignWidget::prepareMenuTree(const QPoint &pos) action->setChecked(true);
connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); });
}
- menu.exec(treeView->mapToGlobal(pos));
+ menu.exec(treeView[num]->mapToGlobal(pos));
}
void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column)
{
QtProperty *selectedProperty = propertyEditor->itemToBrowserItem(item)->property();
ElementType type = getElementTypeByName(selectedProperty->propertyId());
- auto it = treeModel->nodeForIdType(type, ctx->id(selectedProperty->valueText().toStdString()));
- if (it)
- selectionModel->setCurrentIndex(treeModel->indexFromNode(*it), QItemSelectionModel::ClearAndSelect);
+ if (type == ElementType::NONE)
+ return;
+ auto it = getTreeByElementType(type)->nodeForId(ctx->id(selectedProperty->valueText().toStdString()));
+ if (it) {
+ int num = getIndexByElementType(type);
+ clearAllSelectionModels();
+ if (tabWidget->currentIndex() != num)
+ tabWidget->setCurrentIndex(num);
+ selectionModel[num]->setCurrentIndex(getTreeByElementType(type)->indexFromNode(*it),
+ QItemSelectionModel::ClearAndSelect);
+ }
}
void DesignWidget::onDoubleClicked(const QModelIndex &index) { Q_EMIT zoomSelected(); }
void DesignWidget::onSearchInserted()
{
- if (currentSearch == searchEdit->text()) {
+ if (currentSearch == searchEdit->text() && currentIndexTab == tabWidget->currentIndex()) {
currentIndex++;
if (currentIndex >= currentSearchIndexes.size())
currentIndex = 0;
@@ -812,17 +1011,19 @@ void DesignWidget::onSearchInserted() std::lock_guard<std::mutex> lock(ctx->mutex);
currentSearch = searchEdit->text();
- currentSearchIndexes = treeModel->search(searchEdit->text());
+ currentSearchIndexes = treeModel[tabWidget->currentIndex()]->search(searchEdit->text());
currentIndex = 0;
+ currentIndexTab = tabWidget->currentIndex();
}
if (currentSearchIndexes.size() > 0 && currentIndex < currentSearchIndexes.size())
- selectionModel->setCurrentIndex(currentSearchIndexes.at(currentIndex), QItemSelectionModel::ClearAndSelect);
+ selectionModel[tabWidget->currentIndex()]->setCurrentIndex(currentSearchIndexes.at(currentIndex),
+ QItemSelectionModel::ClearAndSelect);
}
-void DesignWidget::onHoverIndexChanged(QModelIndex index)
+void DesignWidget::onHoverIndexChanged(int num, QModelIndex index)
{
if (index.isValid()) {
- TreeModel::Item *item = treeModel->nodeFromIndex(index);
+ TreeModel::Item *item = treeModel[num]->nodeFromIndex(index);
if (item->type() != ElementType::NONE) {
std::vector<DecalXY> decals = getDecals(item->type(), item->id());
if (decals.size() > 0)
@@ -841,7 +1042,7 @@ void DesignWidget::onHoverPropertyChanged(QtBrowserItem *item) if (type != ElementType::NONE) {
IdString value = ctx->id(selectedProperty->valueText().toStdString());
if (value != IdString()) {
- auto node = treeModel->nodeForIdType(type, value);
+ auto node = getTreeByElementType(type)->nodeForId(value);
if (node) {
std::vector<DecalXY> decals = getDecals((*node)->type(), (*node)->id());
if (decals.size() > 0)
diff --git a/gui/designwidget.h b/gui/designwidget.h index 0248d2c7..89c6e702 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -21,6 +21,7 @@ #define DESIGNWIDGET_H
#include <QMouseEvent>
+#include <QTabWidget>
#include <QTreeView>
#include <QVariant>
#include "nextpnr.h"
@@ -65,11 +66,14 @@ class DesignWidget : public QWidget const ElementType &type = ElementType::NONE);
QString getElementTypeName(ElementType type);
ElementType getElementTypeByName(QString type);
+ TreeModel::Model *getTreeByElementType(ElementType type);
+ int getIndexByElementType(ElementType type);
int getElementIndex(ElementType type);
void updateButtons();
- void addToHistory(QModelIndex item);
+ void addToHistory(int tab, QModelIndex item);
std::vector<DecalXY> getDecals(ElementType type, IdString value);
void updateHighlightGroup(QList<TreeModel::Item *> item, int group);
+ void clearAllSelectionModels();
Q_SIGNALS:
void selected(std::vector<DecalXY> decal, bool keep);
void highlight(std::vector<DecalXY> decal, int group);
@@ -78,12 +82,12 @@ class DesignWidget : public QWidget private Q_SLOTS:
void prepareMenuProperty(const QPoint &pos);
- void prepareMenuTree(const QPoint &pos);
- void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
+ void prepareMenuTree(int num, const QPoint &pos);
+ void onSelectionChanged(int num, const QItemSelection &selected, const QItemSelection &deselected);
void onItemDoubleClicked(QTreeWidgetItem *item, int column);
void onDoubleClicked(const QModelIndex &index);
void onSearchInserted();
- void onHoverIndexChanged(QModelIndex index);
+ void onHoverIndexChanged(int num, QModelIndex index);
void onHoverPropertyChanged(QtBrowserItem *item);
public Q_SLOTS:
void newContext(Context *ctx);
@@ -95,9 +99,11 @@ class DesignWidget : public QWidget private:
Context *ctx;
- TreeView *treeView;
- QItemSelectionModel *selectionModel;
- TreeModel::Model *treeModel;
+ QTabWidget *tabWidget;
+
+ TreeView *treeView[6];
+ QItemSelectionModel *selectionModel[6];
+ TreeModel::Model *treeModel[6];
QLineEdit *searchEdit;
QtVariantPropertyManager *variantManager;
QtVariantPropertyManager *readOnlyManager;
@@ -108,7 +114,7 @@ class DesignWidget : public QWidget QMap<QtProperty *, QString> propertyToId;
QMap<QString, QtProperty *> idToProperty;
- std::vector<QModelIndex> history;
+ std::vector<std::pair<int, QModelIndex>> history;
int history_index;
bool history_ignore;
@@ -124,6 +130,7 @@ class DesignWidget : public QWidget QString currentSearch;
QList<QModelIndex> currentSearchIndexes;
int currentIndex;
+ int currentIndexTab;
};
NEXTPNR_NAMESPACE_END
diff --git a/gui/ecp5/mainwindow.cc b/gui/ecp5/mainwindow.cc index b3c53849..fe2f9e57 100644 --- a/gui/ecp5/mainwindow.cc +++ b/gui/ecp5/mainwindow.cc @@ -20,6 +20,7 @@ #include "mainwindow.h"
#include "bitstream.h"
#include "log.h"
+#include <fstream>
#include <QFileDialog>
#include <QInputDialog>
@@ -53,6 +54,12 @@ void MainWindow::newContext(Context *ctx) void MainWindow::createMenu()
{
// Add arch specific actions
+ actionLoadLPF = new QAction("Open LPF", this);
+ actionLoadLPF->setIcon(QIcon(":/icons/resources/open_lpf.png"));
+ actionLoadLPF->setStatusTip("Open LPF file");
+ actionLoadLPF->setEnabled(false);
+ connect(actionLoadLPF, &QAction::triggered, this, &MainWindow::open_lpf);
+
actionLoadBase = new QAction("Open Base Config", this);
actionLoadBase->setIcon(QIcon(":/icons/resources/open_base.png"));
actionLoadBase->setStatusTip("Open Base Config file");
@@ -67,10 +74,12 @@ void MainWindow::createMenu() // Add actions in menus
mainActionBar->addSeparator();
+ mainActionBar->addAction(actionLoadLPF);
mainActionBar->addAction(actionLoadBase);
mainActionBar->addAction(actionSaveConfig);
menuDesign->addSeparator();
+ menuDesign->addAction(actionLoadLPF);
menuDesign->addAction(actionLoadBase);
menuDesign->addAction(actionSaveConfig);
}
@@ -81,11 +90,11 @@ static QStringList getSupportedPackages(ArchArgs::ArchArgsTypes chip) {
QStringList packages;
const ChipInfoPOD *chip_info;
- if (chip == ArchArgs::LFE5U_25F) {
+ if (chip == ArchArgs::LFE5U_25F || chip == ArchArgs::LFE5UM_25F || chip == ArchArgs::LFE5UM5G_25F) {
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_25k));
- } else if (chip == ArchArgs::LFE5U_45F) {
+ } else if (chip == ArchArgs::LFE5U_45F || chip == ArchArgs::LFE5UM_45F || chip == ArchArgs::LFE5UM5G_45F) {
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_45k));
- } else if (chip == ArchArgs::LFE5U_85F) {
+ } else if (chip == ArchArgs::LFE5U_85F || chip == ArchArgs::LFE5UM_85F || chip == ArchArgs::LFE5UM5G_85F) {
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(chipdb_blob_85k));
} else {
log_error("Unsupported ECP5 chip type.\n");
@@ -100,9 +109,15 @@ static QStringList getSupportedPackages(ArchArgs::ArchArgsTypes chip) void MainWindow::new_proj()
{
QMap<QString, int> arch;
- arch.insert("Lattice ECP5 25K", ArchArgs::LFE5U_25F);
- arch.insert("Lattice ECP5 45K", ArchArgs::LFE5U_45F);
- arch.insert("Lattice ECP5 85K", ArchArgs::LFE5U_85F);
+ arch.insert("Lattice ECP5 LFE5U-25F", ArchArgs::LFE5U_25F);
+ arch.insert("Lattice ECP5 LFE5U-45F", ArchArgs::LFE5U_45F);
+ arch.insert("Lattice ECP5 LFE5U-85F", ArchArgs::LFE5U_85F);
+ arch.insert("Lattice ECP5 LFE5UM-25F", ArchArgs::LFE5UM_25F);
+ arch.insert("Lattice ECP5 LFE5UM-45F", ArchArgs::LFE5UM_45F);
+ arch.insert("Lattice ECP5 LFE5UM-85F", ArchArgs::LFE5UM_85F);
+ arch.insert("Lattice ECP5 LFE5UM5G-25F", ArchArgs::LFE5UM5G_25F);
+ arch.insert("Lattice ECP5 LFE5UM5G-45F", ArchArgs::LFE5UM5G_45F);
+ arch.insert("Lattice ECP5 LFE5UM5G-85F", ArchArgs::LFE5UM5G_85F);
bool ok;
QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch.keys(), 0, false, &ok);
if (ok && !item.isEmpty()) {
@@ -131,6 +146,22 @@ void MainWindow::load_base_config(std::string filename) actionSaveConfig->setEnabled(true);
}
+void MainWindow::open_lpf()
+{
+ QString fileName = QFileDialog::getOpenFileName(this, QString("Open LPF"), QString(), QString("*.lpf"));
+ if (!fileName.isEmpty()) {
+ std::ifstream in(fileName.toStdString());
+ if (ctx->applyLPF(fileName.toStdString(), in)) {
+ log("Loading LPF successful.\n");
+ actionPack->setEnabled(true);
+ actionLoadLPF->setEnabled(false);
+ } else {
+ actionLoadLPF->setEnabled(true);
+ log("Loading LPF failed.\n");
+ }
+ }
+}
+
void MainWindow::open_base()
{
QString fileName = QFileDialog::getOpenFileName(this, QString("Open Base Config"), QString(), QString("*.config"));
@@ -152,10 +183,19 @@ void MainWindow::save_config() void MainWindow::onDisableActions()
{
+ actionLoadLPF->setEnabled(false);
actionLoadBase->setEnabled(false);
actionSaveConfig->setEnabled(false);
}
+void MainWindow::onJsonLoaded() { actionLoadLPF->setEnabled(true); }
+
void MainWindow::onRouteFinished() { actionLoadBase->setEnabled(true); }
+void MainWindow::onProjectLoaded()
+{
+ if (ctx->settings.find(ctx->id("input/lpf")) != ctx->settings.end())
+ actionLoadLPF->setEnabled(false);
+}
+
NEXTPNR_NAMESPACE_END
diff --git a/gui/ecp5/mainwindow.h b/gui/ecp5/mainwindow.h index f85c2abc..721c6c0b 100644 --- a/gui/ecp5/mainwindow.h +++ b/gui/ecp5/mainwindow.h @@ -38,15 +38,19 @@ class MainWindow : public BaseMainWindow protected:
void onDisableActions() override;
+ void onJsonLoaded() override;
void onRouteFinished() override;
+ void onProjectLoaded() override;
protected Q_SLOTS:
- virtual void new_proj();
+ void new_proj() override;
void newContext(Context *ctx);
+ void open_lpf();
void open_base();
void save_config();
private:
+ QAction *actionLoadLPF;
QAction *actionLoadBase;
QAction *actionSaveConfig;
diff --git a/gui/ecp5/nextpnr.qrc b/gui/ecp5/nextpnr.qrc index 09f96d74..ca7e5b1a 100644 --- a/gui/ecp5/nextpnr.qrc +++ b/gui/ecp5/nextpnr.qrc @@ -1,5 +1,6 @@ <RCC> <qresource prefix="/icons"> + <file>resources/open_lpf.png</file> <file>resources/open_base.png</file> <file>resources/save_config.png</file> </qresource> diff --git a/gui/ecp5/resources/open_base.png b/gui/ecp5/resources/open_base.png Binary files differindex d58d226c..b60cf25a 100644 --- a/gui/ecp5/resources/open_base.png +++ b/gui/ecp5/resources/open_base.png diff --git a/gui/ecp5/resources/open_lpf.png b/gui/ecp5/resources/open_lpf.png Binary files differnew file mode 100644 index 00000000..54b6f6f9 --- /dev/null +++ b/gui/ecp5/resources/open_lpf.png diff --git a/gui/ecp5/resources/save_config.png b/gui/ecp5/resources/save_config.png Binary files differindex 2ade7128..63b5ab56 100644 --- a/gui/ecp5/resources/save_config.png +++ b/gui/ecp5/resources/save_config.png diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 24fbc35d..b771d6a4 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -56,7 +56,8 @@ FPGAViewWidget::FPGAViewWidget(QWidget *parent) colors_.highlight[7] = QColor("#da70d6"); rendererArgs_->changed = false; - rendererArgs_->flags.zoomOutbound = true; + rendererArgs_->gridChanged = false; + rendererArgs_->zoomOutbound = true; auto fmt = format(); fmt.setMajorVersion(3); @@ -86,12 +87,17 @@ FPGAViewWidget::~FPGAViewWidget() {} void FPGAViewWidget::newContext(Context *ctx) { ctx_ = ctx; + { + QMutexLocker lock(&rendererArgsLock_); + + rendererArgs_->gridChanged = true; + } onSelectedArchItem(std::vector<DecalXY>(), false); for (int i = 0; i < 8; i++) onHighlightGroupChanged(std::vector<DecalXY>(), i); { QMutexLocker lock(&rendererArgsLock_); - rendererArgs_->flags.zoomOutbound = true; + rendererArgs_->zoomOutbound = true; } pokeRenderer(); } @@ -109,19 +115,6 @@ void FPGAViewWidget::initializeGL() QtImGui::initialize(this); glClearColor(colors_.background.red() / 255, colors_.background.green() / 255, colors_.background.blue() / 255, 0.0); - - - { - QMutexLocker locker(&rendererDataLock_); - // Render grid. - auto grid = LineShaderData(); - for (float i = -100.0f; i < 100.0f; i += 1.0f) { - PolyLine(-100.0f, i, 100.0f, i).build(grid); - PolyLine(i, -100.0f, i, 100.0f).build(grid); - } - grid.last_render = 1; - lineShader_.update_vbos(GraphicElement::STYLE_GRID, grid); - } } float FPGAViewWidget::PickedElement::distance(Context *ctx, float wx, float wy) const @@ -293,7 +286,7 @@ QMatrix4x4 FPGAViewWidget::getProjection(void) QMatrix4x4 matrix; const float aspect = float(width()) / float(height()); - matrix.perspective(90, aspect, zoomNear_, zoomFar_ + 0.1f); + matrix.perspective(90, aspect, zoomNear_ - 0.01f, zoomFar_ + 0.01f); return matrix; } @@ -346,22 +339,7 @@ void FPGAViewWidget::paintGL() lineShader_.draw(GraphicElement::STYLE_HOVER, colors_.hovered, thick2Px, matrix); - // Flags from pipeline. - PassthroughFlags flags = rendererData_->flags; - - // Check flags passed through pipeline. - if (flags.zoomOutbound) { - // If we're doing init zoomOutbound, make sure we're actually drawing - // something already. - if (rendererData_->gfxByStyle[GraphicElement::STYLE_FRAME].vertices.size() != 0) { - zoomOutbound(); - flags.zoomOutbound = false; - { - QMutexLocker lock(&rendererArgsLock_); - rendererArgs_->flags.zoomOutbound = false; - } - } - } + // Render ImGui QtImGui::newFrame(); QMutexLocker lock(&rendererArgsLock_); if (!(rendererArgs_->hoveredDecal == DecalXY()) && rendererArgs_->hintText.size() > 0) @@ -444,7 +422,7 @@ void FPGAViewWidget::renderLines(void) DecalXY hoveredDecal; std::vector<DecalXY> highlightedDecals[8]; bool highlightedOrSelectedChanged; - PassthroughFlags flags; + bool gridChanged; { // Take the renderer arguments lock, copy over all we need. QMutexLocker lock(&rendererArgsLock_); @@ -456,8 +434,9 @@ void FPGAViewWidget::renderLines(void) highlightedDecals[i] = rendererArgs_->highlightedDecals[i]; highlightedOrSelectedChanged = rendererArgs_->changed; + gridChanged = rendererArgs_->gridChanged; rendererArgs_->changed = false; - flags = rendererArgs_->flags; + rendererArgs_->gridChanged = false; } // Render decals if necessary. @@ -528,6 +507,7 @@ void FPGAViewWidget::renderLines(void) // If we're not re-rendering any highlights/selections, let's // copy them over from teh current object. + data->gfxGrid = rendererData_->gfxGrid; if (!highlightedOrSelectedChanged) { data->gfxSelected = rendererData_->gfxSelected; data->gfxHovered = rendererData_->gfxHovered; @@ -539,7 +519,19 @@ void FPGAViewWidget::renderLines(void) rendererData_ = std::move(data); } } - + if (gridChanged) + { + QMutexLocker locker(&rendererDataLock_); + rendererData_->gfxGrid.clear(); + // Render grid. + for (float i = 0.0f; i < 1.0f * ctx_->getGridDimX() + 1; i += 1.0f) { + PolyLine(i, 0.0f, i, 1.0f * ctx_->getGridDimY()).build(rendererData_->gfxGrid); + } + for (float i = 0.0f; i < 1.0f * ctx_->getGridDimY() + 1; i += 1.0f) { + PolyLine(0.0f, i, 1.0f * ctx_->getGridDimX(), i).build(rendererData_->gfxGrid); + } + rendererData_->gfxGrid.last_render++; + } if (highlightedOrSelectedChanged) { QMutexLocker locker(&rendererDataLock_); @@ -573,8 +565,12 @@ void FPGAViewWidget::renderLines(void) } { - QMutexLocker locker(&rendererDataLock_); - rendererData_->flags = flags; + QMutexLocker lock(&rendererArgsLock_); + + if (rendererArgs_->zoomOutbound) { + zoomOutbound(); + rendererArgs_->zoomOutbound = false; + } } } @@ -851,7 +847,8 @@ void FPGAViewWidget::zoomSelected() { { QMutexLocker lock(&rendererDataLock_); - zoomToBB(rendererData_->bbSelected, 0.5f, true); + if (rendererData_->bbSelected.x0() != std::numeric_limits<float>::infinity()) + zoomToBB(rendererData_->bbSelected, 0.5f, true); } update(); } @@ -876,6 +873,8 @@ void FPGAViewWidget::leaveEvent(QEvent *event) void FPGAViewWidget::update_vbos() { + lineShader_.update_vbos(GraphicElement::STYLE_GRID, rendererData_->gfxGrid); + for (int style = GraphicElement::STYLE_FRAME; style < GraphicElement::STYLE_HIGHLIGHTED0; style++) { diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index e76e6f32..3c0cfbbd 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -127,7 +127,7 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions private: const float zoomNear_ = 0.1f; // do not zoom closer than this - float zoomFar_ = 10.0f; // do not zoom further than this + float zoomFar_ = 10.0f; // do not zoom further than this const float zoomLvl1_ = 1.0f; const float zoomLvl2_ = 5.0f; @@ -240,21 +240,6 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions QColor highlight[8]; } colors_; - // Flags that are passed through from renderer arguments to renderer data. - // These are used by the UI code to signal events that will only fire when - // the next frame gets rendered. - struct PassthroughFlags - { - bool zoomOutbound; - - PassthroughFlags() : zoomOutbound(false) {} - PassthroughFlags &operator=(const PassthroughFlags &other) noexcept - { - zoomOutbound = other.zoomOutbound; - return *this; - } - }; - struct RendererArgs { // Decals that he user selected. @@ -265,19 +250,22 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions DecalXY hoveredDecal; // Whether to render the above three or skip it. bool changed; + // Whether to render grid or skip it. + bool gridChanged; - // Flags to pass back into the RendererData. - PassthroughFlags flags; + // Flags for rendering. + bool zoomOutbound; // Hint text std::string hintText; // cursor pos - int x,y; + int x, y; }; std::unique_ptr<RendererArgs> rendererArgs_; QMutex rendererArgsLock_; struct RendererData { + LineShaderData gfxGrid; LineShaderData gfxByStyle[GraphicElement::STYLE_MAX]; LineShaderData gfxSelected; LineShaderData gfxHovered; @@ -288,8 +276,6 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions PickQuadTree::BoundingBox bbSelected; // Quadtree for picking objects. std::unique_ptr<PickQuadTree> qt; - // Flags from args. - PassthroughFlags flags; }; std::unique_ptr<RendererData> rendererData_; QMutex rendererDataLock_; diff --git a/gui/generic/mainwindow.h b/gui/generic/mainwindow.h index a4ce9958..bb6a4cf1 100644 --- a/gui/generic/mainwindow.h +++ b/gui/generic/mainwindow.h @@ -36,7 +36,7 @@ class MainWindow : public BaseMainWindow void createMenu();
protected Q_SLOTS:
- virtual void new_proj();
+ void new_proj() override;
void newContext(Context *ctx);
};
diff --git a/gui/ice40/mainwindow.h b/gui/ice40/mainwindow.h index bb8ed75f..4a9a7d8e 100644 --- a/gui/ice40/mainwindow.h +++ b/gui/ice40/mainwindow.h @@ -44,7 +44,7 @@ class MainWindow : public BaseMainWindow void onProjectLoaded() override;
protected Q_SLOTS:
- virtual void new_proj();
+ void new_proj() override;
void open_pcf();
void save_asc();
diff --git a/gui/ice40/resources/open_pcf.png b/gui/ice40/resources/open_pcf.png Binary files differindex 093dec39..9a4c64d5 100644 --- a/gui/ice40/resources/open_pcf.png +++ b/gui/ice40/resources/open_pcf.png diff --git a/gui/ice40/resources/save_asc.png b/gui/ice40/resources/save_asc.png Binary files differindex 15b59ca1..c3e6b0ab 100644 --- a/gui/ice40/resources/save_asc.png +++ b/gui/ice40/resources/save_asc.png diff --git a/gui/quadtree.h b/gui/quadtree.h index a6c38a85..9fcddf73 100644 --- a/gui/quadtree.h +++ b/gui/quadtree.h @@ -266,20 +266,20 @@ template <typename CoordinateT, typename ElementT> class QuadTreeNode splitx_ = (bound_.x1_ - bound_.x0_) / 2 + bound_.x0_; splity_ = (bound_.y1_ - bound_.y0_) / 2 + bound_.y0_; // Create the new children. - children_ = decltype(children_)(new QuadTreeNode<CoordinateT, ElementT>[4] { - // Note: not using [NW] = QuadTreeNode because that seems to - // crash g++ 7.3.0. - /* NW */ QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_), - depth_ + 1, max_elems_), - /* NE */ - QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_), - depth_ + 1, max_elems_), - /* SW */ - QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_), - depth_ + 1, max_elems_), - /* SE */ - QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_), - depth_ + 1, max_elems_), + children_ = decltype(children_)(new QuadTreeNode<CoordinateT, ElementT>[4]{ + // Note: not using [NW] = QuadTreeNode because that seems to + // crash g++ 7.3.0. + /* NW */ QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_), + depth_ + 1, max_elems_), + /* NE */ + QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_), + depth_ + 1, max_elems_), + /* SW */ + QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_), + depth_ + 1, max_elems_), + /* SE */ + QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_), + depth_ + 1, max_elems_), }); // Move all elements to where they belong. auto it = elems_.begin(); diff --git a/gui/resources/open_json.png b/gui/resources/open_json.png Binary files differindex 90c07267..7352824b 100644 --- a/gui/resources/open_json.png +++ b/gui/resources/open_json.png diff --git a/gui/treemodel.cc b/gui/treemodel.cc index 33dd6a96..b834c682 100644 --- a/gui/treemodel.cc +++ b/gui/treemodel.cc @@ -154,80 +154,21 @@ Model::Model(QObject *parent) : QAbstractItemModel(parent), root_(new Item("Elem Model::~Model() {} -void Model::loadContext(Context *ctx) +void Model::loadData(Context *ctx, std::unique_ptr<Item> data) { - if (!ctx) - return; - ctx_ = ctx; - beginResetModel(); - - // Currently we lack an API to get a proper hierarchy of bels/pip/wires - // cross-arch. So we only do this for ICE40 by querying the ChipDB - // directly. - // TODO(q3k): once AnyId and the tree API land in Arch, move this over. -#ifdef ARCH_ICE40 - { - std::map<std::pair<int, int>, std::vector<BelId>> belMap; - for (auto bel : ctx->getBels()) { - auto loc = ctx->getBelLocation(bel); - belMap[std::pair<int, int>(loc.x, loc.y)].push_back(bel); - } - auto belGetter = [](Context *ctx, BelId id) { return ctx->getBelName(id); }; - bel_root_ = std::unique_ptr<BelXYRoot>( - new BelXYRoot(ctx, "Bels", root_.get(), belMap, belGetter, ElementType::BEL)); - - std::map<std::pair<int, int>, std::vector<WireId>> wireMap; - for (int i = 0; i < ctx->chip_info->num_wires; i++) { - const auto wire = &ctx->chip_info->wire_data[i]; - WireId wireid; - wireid.index = i; - wireMap[std::pair<int, int>(wire->x, wire->y)].push_back(wireid); - } - auto wireGetter = [](Context *ctx, WireId id) { return ctx->getWireName(id); }; - wire_root_ = std::unique_ptr<WireXYRoot>( - new WireXYRoot(ctx, "Wires", root_.get(), wireMap, wireGetter, ElementType::WIRE)); - - std::map<std::pair<int, int>, std::vector<PipId>> pipMap; - for (int i = 0; i < ctx->chip_info->num_pips; i++) { - const auto pip = &ctx->chip_info->pip_data[i]; - PipId pipid; - pipid.index = i; - pipMap[std::pair<int, int>(pip->x, pip->y)].push_back(pipid); - } - auto pipGetter = [](Context *ctx, PipId id) { return ctx->getPipName(id); }; - pip_root_ = std::unique_ptr<PipXYRoot>( - new PipXYRoot(ctx, "Pips", root_.get(), pipMap, pipGetter, ElementType::PIP)); - } -#endif - - cell_root_ = std::unique_ptr<IdStringList>(new IdStringList(QString("Cells"), root_.get(), ElementType::CELL)); - net_root_ = std::unique_ptr<IdStringList>(new IdStringList(QString("Nets"), root_.get(), ElementType::NET)); - + ctx_ = ctx; + root_ = std::move(data); endResetModel(); - - updateCellsNets(ctx); } -void Model::updateCellsNets(Context *ctx) +void Model::updateElements(std::vector<IdString> elements) { - if (!ctx) + if (!ctx_) return; beginResetModel(); - - std::vector<IdString> cells; - for (auto &pair : ctx->cells) { - cells.push_back(pair.first); - } - cell_root_->updateElements(ctx, cells); - - std::vector<IdString> nets; - for (auto &pair : ctx->nets) { - nets.push_back(pair.first); - } - net_root_->updateElements(ctx, nets); - + root_->updateElements(ctx_, elements); endResetModel(); } @@ -302,11 +243,7 @@ QList<QModelIndex> Model::search(QString text) { const int limit = 500; QList<Item *> list; - cell_root_->search(list, text, limit); - net_root_->search(list, text, limit); - bel_root_->search(list, text, limit); - wire_root_->search(list, text, limit); - pip_root_->search(list, text, limit); + root_->search(list, text, limit); QList<QModelIndex> res; for (auto i : list) { diff --git a/gui/treemodel.h b/gui/treemodel.h index 0236a715..d7f337a3 100644 --- a/gui/treemodel.h +++ b/gui/treemodel.h @@ -102,6 +102,10 @@ class Item virtual bool canFetchMore() const { return false; } virtual void fetchMore() {} + virtual boost::optional<Item *> getById(IdString id) { return boost::none; } + virtual void search(QList<Item *> &results, QString text, int limit) {} + virtual void updateElements(Context *ctx, std::vector<IdString> elements) {} + virtual ~Item() { if (parent_ != nullptr) { @@ -143,20 +147,20 @@ class IdStringList : public Item public: // Create an IdStringList at given partent that will contain elements of // the given type. - IdStringList(QString name, Item *parent, ElementType type) : Item(name, parent), child_type_(type) {} + IdStringList(ElementType type) : Item("root", nullptr), child_type_(type) {} // Split a name into alpha/non-alpha parts, which is then used for sorting // of children. static std::vector<QString> alphaNumSplit(const QString &str); // getById finds a child for the given IdString. - IdStringItem *getById(IdString id) const { return managed_.at(id).get(); } + virtual boost::optional<Item *> getById(IdString id) override { return managed_.at(id).get(); } // (Re-)create children from a list of IdStrings. - void updateElements(Context *ctx, std::vector<IdString> elements); + virtual void updateElements(Context *ctx, std::vector<IdString> elements) override; // Find children that contain the given text. - void search(QList<Item *> &results, QString text, int limit); + virtual void search(QList<Item *> &results, QString text, int limit) override; }; // ElementList is a dynamic list of ElementT (BelId,WireId,...) that are @@ -220,7 +224,7 @@ template <typename ElementT> class ElementList : public Item virtual void fetchMore() override { fetchMore(100); } // getById finds a child for the given IdString. - boost::optional<Item *> getById(IdString id) + virtual boost::optional<Item *> getById(IdString id) override { // Search requires us to load all our elements... while (canFetchMore()) @@ -234,7 +238,7 @@ template <typename ElementT> class ElementList : public Item } // Find children that contain the given text. - void search(QList<Item *> &results, QString text, int limit) + virtual void search(QList<Item *> &results, QString text, int limit) override { // Last chance to bail out from loading entire tree into memory. if (limit != -1 && results.size() > limit) @@ -278,8 +282,8 @@ template <typename ElementT> class ElementXYRoot : public Item ElementType child_type_; public: - ElementXYRoot(Context *ctx, QString name, Item *parent, ElementMap map, ElementGetter getter, ElementType type) - : Item(name, parent), ctx_(ctx), map_(map), getter_(getter), child_type_(type) + ElementXYRoot(Context *ctx, ElementMap map, ElementGetter getter, ElementType type) + : Item("root", nullptr), ctx_(ctx), map_(map), getter_(getter), child_type_(type) { // Create all X and Y label Items/ElementLists. @@ -315,7 +319,7 @@ template <typename ElementT> class ElementXYRoot : public Item } // getById finds a child for the given IdString. - boost::optional<Item *> getById(IdString id) + virtual boost::optional<Item *> getById(IdString id) override { // For now, scan linearly all ElementLists. // TODO(q3k) fix this once we have tree API from arch @@ -329,7 +333,7 @@ template <typename ElementT> class ElementXYRoot : public Item } // Find children that contain the given text. - void search(QList<Item *> &results, QString text, int limit) + virtual void search(QList<Item *> &results, QString text, int limit) override { for (auto &l : managed_lists_) { if (limit != -1 && results.size() > limit) @@ -345,15 +349,11 @@ class Model : public QAbstractItemModel Context *ctx_ = nullptr; public: - using BelXYRoot = ElementXYRoot<BelId>; - using WireXYRoot = ElementXYRoot<WireId>; - using PipXYRoot = ElementXYRoot<PipId>; - Model(QObject *parent = nullptr); ~Model(); - void loadContext(Context *ctx); - void updateCellsNets(Context *ctx); + void loadData(Context *ctx, std::unique_ptr<Item> data); + void updateElements(std::vector<IdString> elements); Item *nodeFromIndex(const QModelIndex &idx) const; QModelIndex indexFromNode(Item *node) { @@ -366,29 +366,7 @@ class Model : public QAbstractItemModel QList<QModelIndex> search(QString text); - boost::optional<Item *> nodeForIdType(ElementType type, IdString id) const - { - switch (type) { - case ElementType::BEL: - if (bel_root_ == nullptr) - return boost::none; - return bel_root_->getById(id); - case ElementType::WIRE: - if (wire_root_ == nullptr) - return boost::none; - return wire_root_->getById(id); - case ElementType::PIP: - if (pip_root_ == nullptr) - return boost::none; - return pip_root_->getById(id); - case ElementType::CELL: - return cell_root_->getById(id); - case ElementType::NET: - return net_root_->getById(id); - default: - return boost::none; - } - } + boost::optional<Item *> nodeForId(IdString id) const { return root_->getById(id); } // Override QAbstractItemModel methods int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; @@ -404,11 +382,6 @@ class Model : public QAbstractItemModel private: // Tree elements that we manage the memory for. std::unique_ptr<Item> root_; - std::unique_ptr<BelXYRoot> bel_root_; - std::unique_ptr<WireXYRoot> wire_root_; - std::unique_ptr<PipXYRoot> pip_root_; - std::unique_ptr<IdStringList> cell_root_; - std::unique_ptr<IdStringList> net_root_; }; }; // namespace TreeModel diff --git a/ice40/arch.cc b/ice40/arch.cc index eb26ae5a..2a9e167b 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -575,11 +575,10 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay { const auto &driver = net_info->driver; if (driver.port == id_COUT && sink.port == id_CIN) { - auto driver_loc = getBelLocation(driver.cell->bel); - auto sink_loc = getBelLocation(sink.cell->bel); - if (driver_loc.y == sink_loc.y) + if (driver.cell->constr_abs_z && driver.cell->constr_z < 7) budget = 0; - else + else { + NPNR_ASSERT(driver.cell->constr_z == 7); switch (args.type) { #ifndef ICE40_HX1K_ONLY case ArchArgs::HX8K: @@ -600,6 +599,7 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay default: log_error("Unsupported iCE40 chip type.\n"); } + } return true; } return false; @@ -856,8 +856,9 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort } // Get the port class, also setting clockPort to associated clock if applicable -TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const +TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const { + clockInfoCount = 0; if (cell->type == id_ICESTORM_LC) { if (port == id_CLK) return TMG_CLOCK_INPUT; @@ -870,18 +871,15 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id if (cell->lcInfo.inputCount == 0) return TMG_IGNORE; if (cell->lcInfo.dffEnable) { - clockPort = id_CLK; + clockInfoCount = 1; return TMG_REGISTER_OUTPUT; - } - else + } else return TMG_COMB_OUTPUT; - } - else { + } else { if (cell->lcInfo.dffEnable) { - clockPort = id_CLK; + clockInfoCount = 1; return TMG_REGISTER_INPUT; - } - else + } else return TMG_COMB_INPUT; } } else if (cell->type == id_ICESTORM_RAM) { @@ -889,23 +887,22 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id if (port == id_RCLK || port == id_WCLK) return TMG_CLOCK_INPUT; - if (port.str(this)[0] == 'R') - clockPort = id_RCLK; - else - clockPort = id_WCLK; + clockInfoCount = 1; if (cell->ports.at(port).type == PORT_OUT) return TMG_REGISTER_OUTPUT; else return TMG_REGISTER_INPUT; } else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) { - clockPort = id_CLK; - if (port == id_CLK) + if (port == id_CLK || port == id_CLOCK) return TMG_CLOCK_INPUT; - else if (cell->ports.at(port).type == PORT_OUT) - return TMG_REGISTER_OUTPUT; - else - return TMG_REGISTER_INPUT; + else { + clockInfoCount = 1; + if (cell->ports.at(port).type == PORT_OUT) + return TMG_REGISTER_OUTPUT; + else + return TMG_REGISTER_INPUT; + } } else if (cell->type == id_SB_IO) { if (port == id_D_IN_0 || port == id_D_IN_1) return TMG_STARTPOINT; @@ -934,6 +931,51 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this)); } +TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const +{ + TimingClockingInfo info; + if (cell->type == id_ICESTORM_LC) { + info.clock_port = id_CLK; + info.edge = cell->lcInfo.negClk ? FALLING_EDGE : RISING_EDGE; + if (port == id_O) { + bool has_clktoq = getCellDelay(cell, id_CLK, id_O, info.clockToQ); + NPNR_ASSERT(has_clktoq); + } else { + info.setup.delay = 100; + info.hold.delay = 0; + } + } else if (cell->type == id_ICESTORM_RAM) { + if (port.str(this)[0] == 'R') { + info.clock_port = id_RCLK; + info.edge = bool_or_default(cell->params, id("NEG_CLK_R")) ? FALLING_EDGE : RISING_EDGE; + } else { + info.clock_port = id_WCLK; + info.edge = bool_or_default(cell->params, id("NEG_CLK_W")) ? FALLING_EDGE : RISING_EDGE; + } + if (cell->ports.at(port).type == PORT_OUT) { + bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ); + NPNR_ASSERT(has_clktoq); + } else { + info.setup.delay = 100; + info.hold.delay = 0; + } + } else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) { + info.clock_port = cell->type == id_ICESTORM_SPRAM ? id_CLOCK : id_CLK; + info.edge = RISING_EDGE; + if (cell->ports.at(port).type == PORT_OUT) { + bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ); + if (!has_clktoq) + info.clockToQ.delay = 100; + } else { + info.setup.delay = 100; + info.hold.delay = 0; + } + } else { + NPNR_ASSERT_FALSE("unhandled cell type in getPortClockingInfo"); + } + return info; +} + bool Arch::isGlobalNet(const NetInfo *net) const { if (net == nullptr) diff --git a/ice40/arch.h b/ice40/arch.h index 27d5db9f..836dc46e 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -404,7 +404,7 @@ struct Arch : BaseCtx std::vector<CellInfo *> bel_to_cell; std::vector<NetInfo *> wire_to_net; std::vector<NetInfo *> pip_to_net; - std::vector<NetInfo *> switches_locked; + std::vector<WireId> switches_locked; ArchArgs args; Arch(ArchArgs args); @@ -417,8 +417,8 @@ struct Arch : BaseCtx // ------------------------------------------------- - int getGridDimX() const { return 34; } - int getGridDimY() const { return 34; } + int getGridDimX() const { return chip_info->width; } + int getGridDimY() const { return chip_info->height; } int getTileBelDimZ(int, int) const { return 8; } int getTilePipDimZ(int, int) const { return 1; } @@ -485,6 +485,7 @@ struct Arch : BaseCtx Loc getBelLocation(BelId bel) const { + NPNR_ASSERT(bel != BelId()); Loc loc; loc.x = chip_info->bel_data[bel.index].x; loc.y = chip_info->bel_data[bel.index].y; @@ -546,7 +547,7 @@ struct Arch : BaseCtx auto pip = it->second.pip; if (pip != PipId()) { pip_to_net[pip.index] = nullptr; - switches_locked[chip_info->pip_data[pip.index].switch_index] = nullptr; + switches_locked[chip_info->pip_data[pip.index].switch_index] = WireId(); } net_wires.erase(it); @@ -566,6 +567,8 @@ struct Arch : BaseCtx return wire_to_net[wire.index]; } + WireId getConflictingWireWire(WireId wire) const { return wire; } + NetInfo *getConflictingWireNet(WireId wire) const { NPNR_ASSERT(wire != WireId()); @@ -608,14 +611,15 @@ struct Arch : BaseCtx { NPNR_ASSERT(pip != PipId()); NPNR_ASSERT(pip_to_net[pip.index] == nullptr); - NPNR_ASSERT(switches_locked[chip_info->pip_data[pip.index].switch_index] == nullptr); - - pip_to_net[pip.index] = net; - switches_locked[chip_info->pip_data[pip.index].switch_index] = net; + NPNR_ASSERT(switches_locked[chip_info->pip_data[pip.index].switch_index] == WireId()); WireId dst; dst.index = chip_info->pip_data[pip.index].dst; NPNR_ASSERT(wire_to_net[dst.index] == nullptr); + + pip_to_net[pip.index] = net; + switches_locked[chip_info->pip_data[pip.index].switch_index] = dst; + wire_to_net[dst.index] = net; net->wires[dst].pip = pip; net->wires[dst].strength = strength; @@ -627,7 +631,7 @@ struct Arch : BaseCtx { NPNR_ASSERT(pip != PipId()); NPNR_ASSERT(pip_to_net[pip.index] != nullptr); - NPNR_ASSERT(switches_locked[chip_info->pip_data[pip.index].switch_index] != nullptr); + NPNR_ASSERT(switches_locked[chip_info->pip_data[pip.index].switch_index] != WireId()); WireId dst; dst.index = chip_info->pip_data[pip.index].dst; @@ -636,33 +640,39 @@ struct Arch : BaseCtx pip_to_net[pip.index]->wires.erase(dst); pip_to_net[pip.index] = nullptr; - switches_locked[chip_info->pip_data[pip.index].switch_index] = nullptr; + switches_locked[chip_info->pip_data[pip.index].switch_index] = WireId(); refreshUiPip(pip); refreshUiWire(dst); } - bool checkPipAvail(PipId pip) const + bool ice40_pip_hard_unavail(PipId pip) const { NPNR_ASSERT(pip != PipId()); auto &pi = chip_info->pip_data[pip.index]; auto &si = chip_info->bits_info->switches[pi.switch_index]; - if (switches_locked[pi.switch_index] != nullptr) - return false; - if (pi.flags & PipInfoPOD::FLAG_ROUTETHRU) { NPNR_ASSERT(si.bel >= 0); if (bel_to_cell[si.bel] != nullptr) - return false; + return true; } if (pi.flags & PipInfoPOD::FLAG_NOCARRY) { NPNR_ASSERT(si.bel >= 0); if (bel_carry[si.bel]) - return false; + return true; } - return true; + return false; + } + + bool checkPipAvail(PipId pip) const + { + if (ice40_pip_hard_unavail(pip)) + return false; + + auto &pi = chip_info->pip_data[pip.index]; + return switches_locked[pi.switch_index] == WireId(); } NetInfo *getBoundPipNet(PipId pip) const @@ -671,12 +681,23 @@ struct Arch : BaseCtx return pip_to_net[pip.index]; } - NetInfo *getConflictingPipNet(PipId pip) const + WireId getConflictingPipWire(PipId pip) const { - NPNR_ASSERT(pip != PipId()); + if (ice40_pip_hard_unavail(pip)) + return WireId(); + return switches_locked[chip_info->pip_data[pip.index].switch_index]; } + NetInfo *getConflictingPipNet(PipId pip) const + { + if (ice40_pip_hard_unavail(pip)) + return nullptr; + + WireId wire = switches_locked[chip_info->pip_data[pip.index].switch_index]; + return wire == WireId() ? nullptr : wire_to_net[wire.index]; + } + AllPipRange getPips() const { AllPipRange range; @@ -775,6 +796,12 @@ struct Arch : BaseCtx delay_t getDelayEpsilon() const { return 20; } delay_t getRipupDelayPenalty() const { return 200; } float getDelayNS(delay_t v) const { return v * 0.001; } + DelayInfo getDelayFromNS(float ns) const + { + DelayInfo del; + del.delay = delay_t(ns * 1000); + return del; + } uint32_t getDelayChecksum(delay_t v) const { return v; } bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; @@ -798,8 +825,10 @@ struct Arch : BaseCtx // Get the delay through a cell from one port to another, returning false // if no path exists bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; - // Get the port class, also setting clockDomain if applicable - TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockDomain) const; + // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port + TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const; + // Get the TimingClockingInfo of a port + TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const; // Return true if a port is a net bool isGlobalNet(const NetInfo *net) const; diff --git a/ice40/arch_pybindings.cc b/ice40/arch_pybindings.cc index f1639ba6..3fafb1f6 100644 --- a/ice40/arch_pybindings.cc +++ b/ice40/arch_pybindings.cc @@ -140,6 +140,10 @@ void arch_wrap_python() "cells"); readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls, "nets"); + + fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>, + pass_through<float>>::def_wrap(ctx_cls, "addClock"); + WRAP_RANGE(Bel, conv_to_str<BelId>); WRAP_RANGE(Wire, conv_to_str<WireId>); WRAP_RANGE(AllPip, conv_to_str<PipId>); diff --git a/ice40/archdefs.h b/ice40/archdefs.h index c04033e7..b9614c07 100644 --- a/ice40/archdefs.h +++ b/ice40/archdefs.h @@ -66,6 +66,7 @@ struct BelId bool operator==(const BelId &other) const { return index == other.index; } bool operator!=(const BelId &other) const { return index != other.index; } + bool operator<(const BelId &other) const { return index < other.index; } }; struct WireId @@ -74,6 +75,7 @@ struct WireId bool operator==(const WireId &other) const { return index == other.index; } bool operator!=(const WireId &other) const { return index != other.index; } + bool operator<(const WireId &other) const { return index < other.index; } }; struct PipId @@ -82,6 +84,7 @@ struct PipId bool operator==(const PipId &other) const { return index == other.index; } bool operator!=(const PipId &other) const { return index != other.index; } + bool operator<(const PipId &other) const { return index < other.index; } }; struct GroupId diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index e56ed37d..4cfed52d 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -702,6 +702,8 @@ void write_asc(const Context *ctx, std::ostream &out) setColBufCtrl = (y == 8 || y == 9 || y == 24 || y == 25);
} else if (ctx->args.type == ArchArgs::UP5K) {
setColBufCtrl = (y == 4 || y == 5 || y == 14 || y == 15 || y == 26 || y == 27);
+ } else if (ctx->args.type == ArchArgs::LP384) {
+ setColBufCtrl = false;
}
if (setColBufCtrl) {
set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_0", true);
diff --git a/ice40/cells.cc b/ice40/cells.cc index 76e67ab7..fbb77b0c 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -383,6 +383,10 @@ bool is_clock_port(const BaseCtx *ctx, const PortRef &port) port.port == ctx->id("WCLKN"); if (is_sb_mac16(ctx, port.cell) || port.cell->type == ctx->id("ICESTORM_DSP")) return port.port == ctx->id("CLK"); + if (is_sb_spram(ctx, port.cell) || port.cell->type == ctx->id("ICESTORM_SPRAM")) + return port.port == id_CLOCK; + if (is_sb_io(ctx, port.cell)) + return port.port == id_INPUT_CLK || port.port == id_OUTPUT_CLK; return false; } diff --git a/ice40/main.cc b/ice40/main.cc index 8bab360d..fcc56d04 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -43,7 +43,7 @@ class Ice40CommandHandler : public CommandHandler void customBitstream(Context *ctx) override; protected: - po::options_description getArchOptions(); + po::options_description getArchOptions() override; }; Ice40CommandHandler::Ice40CommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {} diff --git a/ice40/pack.cc b/ice40/pack.cc index edd12f92..7a27d505 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -462,7 +462,8 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port) static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic) { - log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "", is_logic ? " [logic]" : ""); + log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "", + is_logic ? " [logic]" : ""); std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk")); std::unique_ptr<CellInfo> gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name); @@ -489,6 +490,14 @@ static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen } } net->users = keep_users; + + if (net->clkconstr) { + glbnet->clkconstr = std::unique_ptr<ClockConstraint>(new ClockConstraint()); + glbnet->clkconstr->low = net->clkconstr->low; + glbnet->clkconstr->high = net->clkconstr->high; + glbnet->clkconstr->period = net->clkconstr->period; + } + ctx->nets[glbnet->name] = std::move(glbnet); ctx->cells[gb->name] = std::move(gb); } diff --git a/json/jsonparse.cc b/json/jsonparse.cc index a690bf18..e7f39a19 100644 --- a/json/jsonparse.cc +++ b/json/jsonparse.cc @@ -749,6 +749,10 @@ bool parse_json_file(std::istream &f, std::string &filename, Context *ctx) { try { using namespace JsonParser; + + if (!f) + log_error("failed to open JSON file.\n"); + int lineno = 1; JsonNode root(f, lineno); |
