aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/nextpnr.cc137
-rw-r--r--common/nextpnr.h135
-rw-r--r--common/place_common.cc34
-rw-r--r--common/placer1.cc38
-rw-r--r--common/router1.cc1437
-rw-r--r--common/router1.h4
-rw-r--r--common/timing.cc568
-rw-r--r--common/timing.h2
-rw-r--r--docs/archapi.md50
-rw-r--r--docs/constraints.md37
-rw-r--r--docs/faq.md89
-rw-r--r--ecp5/arch.cc111
-rw-r--r--ecp5/arch.h16
-rw-r--r--ecp5/arch_place.cc2
-rw-r--r--ecp5/arch_pybindings.cc4
-rw-r--r--ecp5/archdefs.h13
-rw-r--r--ecp5/bitstream.cc137
-rw-r--r--ecp5/constids.inc307
-rw-r--r--ecp5/dcu_bitstream.h254
-rw-r--r--ecp5/globals.cc61
-rw-r--r--ecp5/main.cc2
-rw-r--r--ecp5/pack.cc170
-rwxr-xr-xecp5/trellis_import.py11
-rw-r--r--generic/arch.cc9
-rw-r--r--generic/arch.h16
-rw-r--r--generic/main.cc2
-rwxr-xr-xgui/create_img.sh6
-rw-r--r--gui/designwidget.cc325
-rw-r--r--gui/designwidget.h23
-rw-r--r--gui/ecp5/mainwindow.cc52
-rw-r--r--gui/ecp5/mainwindow.h6
-rw-r--r--gui/ecp5/nextpnr.qrc1
-rw-r--r--gui/ecp5/resources/open_base.pngbin2022 -> 2175 bytes
-rw-r--r--gui/ecp5/resources/open_lpf.pngbin0 -> 2117 bytes
-rw-r--r--gui/ecp5/resources/save_config.pngbin1428 -> 1645 bytes
-rw-r--r--gui/fpgaviewwidget.cc75
-rw-r--r--gui/fpgaviewwidget.h28
-rw-r--r--gui/generic/mainwindow.h2
-rw-r--r--gui/ice40/mainwindow.h2
-rw-r--r--gui/ice40/resources/open_pcf.pngbin1984 -> 2139 bytes
-rw-r--r--gui/ice40/resources/save_asc.pngbin1384 -> 1509 bytes
-rw-r--r--gui/quadtree.h28
-rw-r--r--gui/resources/open_json.pngbin2093 -> 2177 bytes
-rw-r--r--gui/treemodel.cc77
-rw-r--r--gui/treemodel.h61
-rw-r--r--ice40/arch.cc88
-rw-r--r--ice40/arch.h71
-rw-r--r--ice40/arch_pybindings.cc4
-rw-r--r--ice40/archdefs.h3
-rw-r--r--ice40/bitstream.cc2
-rw-r--r--ice40/cells.cc4
-rw-r--r--ice40/main.cc2
-rw-r--r--ice40/pack.cc11
-rw-r--r--json/jsonparse.cc4
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, &register_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
index d58d226c..b60cf25a 100644
--- a/gui/ecp5/resources/open_base.png
+++ b/gui/ecp5/resources/open_base.png
Binary files differ
diff --git a/gui/ecp5/resources/open_lpf.png b/gui/ecp5/resources/open_lpf.png
new file mode 100644
index 00000000..54b6f6f9
--- /dev/null
+++ b/gui/ecp5/resources/open_lpf.png
Binary files differ
diff --git a/gui/ecp5/resources/save_config.png b/gui/ecp5/resources/save_config.png
index 2ade7128..63b5ab56 100644
--- a/gui/ecp5/resources/save_config.png
+++ b/gui/ecp5/resources/save_config.png
Binary files differ
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
index 093dec39..9a4c64d5 100644
--- a/gui/ice40/resources/open_pcf.png
+++ b/gui/ice40/resources/open_pcf.png
Binary files differ
diff --git a/gui/ice40/resources/save_asc.png b/gui/ice40/resources/save_asc.png
index 15b59ca1..c3e6b0ab 100644
--- a/gui/ice40/resources/save_asc.png
+++ b/gui/ice40/resources/save_asc.png
Binary files differ
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
index 90c07267..7352824b 100644
--- a/gui/resources/open_json.png
+++ b/gui/resources/open_json.png
Binary files differ
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);