aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/nextpnr.cc17
-rw-r--r--common/nextpnr.h13
-rw-r--r--common/place_common.cc15
-rw-r--r--common/placer1.cc12
-rw-r--r--common/router1.cc17
-rw-r--r--common/timing.cc162
-rw-r--r--common/timing.h8
-rw-r--r--ecp5/arch.cc11
-rw-r--r--ecp5/arch.h2
-rw-r--r--ecp5/main.cc6
-rw-r--r--generic/arch.cc13
-rw-r--r--generic/arch.h2
-rw-r--r--generic/main.cc10
-rw-r--r--gui/designwidget.cc67
-rw-r--r--gui/designwidget.h6
-rw-r--r--gui/fpgaviewwidget.cc16
-rw-r--r--gui/fpgaviewwidget.h2
-rw-r--r--gui/ice40/worker.cc3
-rw-r--r--gui/quadtree.h3
-rw-r--r--gui/treemodel.cc446
-rw-r--r--gui/treemodel.h383
-rw-r--r--ice40/arch.cc52
-rw-r--r--ice40/arch.h6
-rw-r--r--ice40/bitstream.cc9
-rw-r--r--ice40/cells.cc4
-rw-r--r--ice40/family.cmake8
-rw-r--r--ice40/main.cc26
-rw-r--r--ice40/pack.cc27
-rw-r--r--ice40/place_legaliser.cc2
-rw-r--r--json/jsonparse.cc6
-rw-r--r--tests/gui/quadtree.cc33
31 files changed, 953 insertions, 434 deletions
diff --git a/common/nextpnr.cc b/common/nextpnr.cc
index cf1b5982..dbea26d8 100644
--- a/common/nextpnr.cc
+++ b/common/nextpnr.cc
@@ -51,7 +51,7 @@ void IdString::initialize_add(const BaseCtx *ctx, const char *s, int idx)
ctx->idstring_idx_to_str->push_back(&insert_rc.first->first);
}
-WireId Context::getNetinfoSourceWire(NetInfo *net_info) const
+WireId Context::getNetinfoSourceWire(const NetInfo *net_info) const
{
if (net_info->driver.cell == nullptr)
return WireId();
@@ -70,9 +70,8 @@ WireId Context::getNetinfoSourceWire(NetInfo *net_info) const
return getBelPinWire(src_bel, portPinFromId(driver_port));
}
-WireId Context::getNetinfoSinkWire(NetInfo *net_info, int user_idx) const
+WireId Context::getNetinfoSinkWire(const NetInfo *net_info, const PortRef &user_info) const
{
- auto &user_info = net_info->users[user_idx];
auto dst_bel = user_info.cell->bel;
if (dst_bel == BelId())
@@ -88,12 +87,14 @@ WireId Context::getNetinfoSinkWire(NetInfo *net_info, int user_idx) const
return getBelPinWire(dst_bel, portPinFromId(user_port));
}
-delay_t Context::getNetinfoRouteDelay(NetInfo *net_info, int user_idx) const
+delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &user_info) const
{
WireId src_wire = getNetinfoSourceWire(net_info);
if (src_wire == WireId())
return 0;
- WireId cursor = getNetinfoSinkWire(net_info, user_idx);
+
+ WireId dst_wire = getNetinfoSinkWire(net_info, user_info);
+ WireId cursor = dst_wire;
delay_t delay = 0;
while (cursor != WireId() && cursor != src_wire) {
@@ -107,11 +108,9 @@ delay_t Context::getNetinfoRouteDelay(NetInfo *net_info, int user_idx) const
}
if (cursor == src_wire)
- delay += getWireDelay(src_wire).maxDelay();
- else
- delay += estimateDelay(src_wire, cursor);
+ return delay + getWireDelay(src_wire).maxDelay();
- return delay;
+ return predictDelay(net_info, user_info);
}
static uint32_t xorshift32(uint32_t x)
diff --git a/common/nextpnr.h b/common/nextpnr.h
index 908b8266..f01173e6 100644
--- a/common/nextpnr.h
+++ b/common/nextpnr.h
@@ -205,10 +205,7 @@ struct DecalXY
DecalId decal;
float x = 0, y = 0;
- bool operator==(const DecalXY &other) const
- {
- return (decal == other.decal && x == other.x && y == other.y);
- }
+ bool operator==(const DecalXY &other) const { return (decal == other.decal && x == other.x && y == other.y); }
};
struct BelPin
@@ -475,14 +472,16 @@ struct Context : Arch, DeterministicRNG
bool force = false;
bool timing_driven = true;
float target_freq = 12e6;
+ bool user_freq = false;
+ int slack_redist_iter = 0;
Context(ArchArgs args) : Arch(args) {}
// --------------------------------------------------------------
- WireId getNetinfoSourceWire(NetInfo *net_info) const;
- WireId getNetinfoSinkWire(NetInfo *net_info, int user_idx) const;
- delay_t getNetinfoRouteDelay(NetInfo *net_info, int user_idx) const;
+ WireId getNetinfoSourceWire(const NetInfo *net_info) const;
+ WireId getNetinfoSinkWire(const NetInfo *net_info, const PortRef &sink) const;
+ delay_t getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &sink) const;
// provided by router1.cc
bool getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t &delay);
diff --git a/common/place_common.cc b/common/place_common.cc
index 5673c847..fd38429f 100644
--- a/common/place_common.cc
+++ b/common/place_common.cc
@@ -37,10 +37,10 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type
return 0;
driver_gb = ctx->getBelGlobalBuf(driver_cell->bel);
driver_loc = ctx->getBelLocation(driver_cell->bel);
- WireId drv_wire = ctx->getBelPinWire(driver_cell->bel, ctx->portPinFromId(net->driver.port));
if (driver_gb)
return 0;
- float worst_slack = 1000;
+ delay_t negative_slack = 0;
+ delay_t worst_slack = std::numeric_limits<delay_t>::max();
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)
@@ -49,11 +49,10 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type
if (load_cell->bel == BelId())
continue;
if (ctx->timing_driven && type == MetricType::COST) {
- WireId user_wire = ctx->getBelPinWire(load_cell->bel, ctx->portPinFromId(load.port));
- delay_t raw_wl = ctx->estimateDelay(drv_wire, user_wire);
- float slack = ctx->getDelayNS(load.budget) - ctx->getDelayNS(raw_wl);
+ delay_t net_delay = ctx->predictDelay(net, load);
+ auto slack = load.budget - net_delay;
if (slack < 0)
- tns += slack;
+ negative_slack += slack;
worst_slack = std::min(slack, worst_slack);
}
@@ -67,11 +66,13 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type
ymax = std::max(ymax, load_loc.y);
}
if (ctx->timing_driven && type == MetricType::COST) {
- wirelength = wirelen_t((((ymax - ymin) + (xmax - xmin)) * std::min(5.0, (1.0 + std::exp(-worst_slack / 5)))));
+ wirelength = wirelen_t(
+ (((ymax - ymin) + (xmax - xmin)) * std::min(5.0, (1.0 + std::exp(-ctx->getDelayNS(worst_slack) / 5)))));
} else {
wirelength = wirelen_t((ymax - ymin) + (xmax - xmin));
}
+ tns += ctx->getDelayNS(negative_slack);
return wirelength;
}
diff --git a/common/placer1.cc b/common/placer1.cc
index ee7225b5..68db9650 100644
--- a/common/placer1.cc
+++ b/common/placer1.cc
@@ -138,6 +138,7 @@ class SAPlacer
if ((placed_cells - constr_placed_cells) % 500 != 0)
log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells),
int(autoplaced.size()));
+ assign_budget(ctx);
ctx->yield();
log_info("Running simulated annealing placer.\n");
@@ -152,6 +153,7 @@ class SAPlacer
}
int n_no_progress = 0;
+ wirelen_t min_metric = curr_metric;
double avg_metric = curr_metric;
temp = 10000;
@@ -177,6 +179,11 @@ class SAPlacer
}
}
+ if (curr_metric < min_metric) {
+ min_metric = curr_metric;
+ improved = true;
+ }
+
// Heuristic to improve placement on the 8k
if (improved)
n_no_progress = 0;
@@ -230,6 +237,8 @@ class SAPlacer
diameter *= post_legalise_dia_scale;
ctx->shuffle(autoplaced);
assign_budget(ctx);
+ } else if (ctx->slack_redist_iter > 0 && iter % ctx->slack_redist_iter == 0) {
+ assign_budget(ctx, true /* quiet */);
}
// Recalculate total metric entirely to avoid rounding errors
@@ -264,6 +273,7 @@ class SAPlacer
}
}
}
+ timing_analysis(ctx, true /* print_fmax */);
ctx->unlock();
return true;
}
@@ -379,8 +389,6 @@ class SAPlacer
// SA acceptance criterea
if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) {
n_accept++;
- if (delta < 2)
- improved = true;
} else {
if (other != IdString())
ctx->unbindBel(oldBel);
diff --git a/common/router1.cc b/common/router1.cc
index 4ef7df64..d35a7621 100644
--- a/common/router1.cc
+++ b/common/router1.cc
@@ -22,6 +22,7 @@
#include "log.h"
#include "router1.h"
+#include "timing.h"
namespace {
@@ -297,7 +298,7 @@ struct Router
src_wires[src_wire] = ctx->getWireDelay(src_wire).maxDelay();
for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) {
- auto dst_wire = ctx->getNetinfoSinkWire(net_info, user_idx);
+ auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]);
if (dst_wire == WireId())
log_error("No wire found for port %s on destination cell %s.\n",
@@ -351,7 +352,7 @@ struct Router
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));
- auto dst_wire = ctx->getNetinfoSinkWire(net_info, user_idx);
+ auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]);
if (dst_wire == WireId())
log_error("No wire found for port %s on destination cell %s.\n",
@@ -482,7 +483,7 @@ void addFullNetRouteJob(Context *ctx, IdString net_name, std::unordered_map<IdSt
if (net_cache[user_idx])
continue;
- auto dst_wire = ctx->getNetinfoSinkWire(net_info, user_idx);
+ auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]);
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),
@@ -539,7 +540,7 @@ void addNetRouteJobs(Context *ctx, IdString net_name, std::unordered_map<IdStrin
if (net_cache[user_idx])
continue;
- auto dst_wire = ctx->getNetinfoSinkWire(net_info, user_idx);
+ auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]);
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),
@@ -614,6 +615,8 @@ bool router1(Context *ctx)
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 */);
bool printNets = ctx->verbose && (jobQueue.size() < 10);
@@ -764,7 +767,7 @@ bool router1(Context *ctx)
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, 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) {
@@ -776,7 +779,8 @@ bool router1(Context *ctx)
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, user_idx)).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);
@@ -811,6 +815,7 @@ bool router1(Context *ctx)
#ifndef NDEBUG
ctx->check();
#endif
+ timing_analysis(ctx, true /* print_fmax */, true /* print_path */);
ctx->unlock();
return true;
} catch (log_execution_error_exception) {
diff --git a/common/timing.cc b/common/timing.cc
index 4486fc24..2beaeee9 100644
--- a/common/timing.cc
+++ b/common/timing.cc
@@ -22,19 +22,29 @@
#include <unordered_map>
#include <utility>
#include "log.h"
+#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
-static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack);
+typedef std::list<const PortRef *> PortRefList;
+
+static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack, bool update, delay_t &min_slack,
+ PortRefList *current_path, PortRefList *crit_path);
// Follow a path, returning budget to annotate
-static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, delay_t slack)
+static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, delay_t slack, bool update,
+ delay_t &min_slack, PortRefList *current_path, PortRefList *crit_path)
{
delay_t value;
if (ctx->getPortClock(user.cell, user.port) != IdString()) {
// At the end of a timing path (arguably, should check setup time
// here too)
value = slack / path_length;
+ if (slack < min_slack) {
+ min_slack = slack;
+ if (crit_path)
+ *crit_path = *current_path;
+ }
} else {
// Default to the path ending here, if no further paths found
value = slack / path_length;
@@ -47,74 +57,158 @@ static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, de
if (is_path) {
NetInfo *net = port.second.net;
if (net) {
- delay_t path_budget = follow_net(ctx, net, path_length, slack - comb_delay.maxDelay());
+ delay_t path_budget = follow_net(ctx, net, path_length, slack - comb_delay.maxDelay(), update,
+ min_slack, current_path, crit_path);
value = std::min(value, path_budget);
}
}
}
}
}
-
- if (value < user.budget) {
- user.budget = value;
- }
return value;
}
-static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack)
+static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack, bool update, delay_t &min_slack,
+ PortRefList *current_path, PortRefList *crit_path)
{
delay_t net_budget = slack / (path_length + 1);
for (auto &usr : net->users) {
- net_budget = std::min(net_budget, follow_user_port(ctx, usr, path_length + 1, slack));
+ if (crit_path)
+ current_path->push_back(&usr);
+ // If budget override is less than existing budget, then do not increment path length
+ int pl = path_length + 1;
+ auto budget = ctx->getBudgetOverride(net, usr, net_budget);
+ if (budget < net_budget) {
+ net_budget = budget;
+ pl = std::max(1, path_length);
+ }
+ auto delay = ctx->getNetinfoRouteDelay(net, usr);
+ net_budget = std::min(
+ net_budget, follow_user_port(ctx, usr, pl, slack - delay, update, min_slack, current_path, crit_path));
+ if (update)
+ usr.budget = std::min(usr.budget, delay + net_budget);
+ if (crit_path)
+ current_path->pop_back();
}
return net_budget;
}
-void assign_budget(Context *ctx)
+static delay_t walk_paths(Context *ctx, bool update, PortRefList *crit_path)
{
- log_break();
- log_info("Annotating ports with timing budgets\n");
- // Clear delays to a very high value first
delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
- for (auto &net : ctx->nets) {
- for (auto &usr : net.second->users) {
- usr.budget = default_slack;
- }
- }
- // Go through all clocked drivers and set up paths
+ delay_t min_slack = default_slack;
+
+ PortRefList current_path;
+
+ // Go through all clocked drivers and distribute the available path
+ // slack evenly into the budget of every sink on the path
for (auto &cell : ctx->cells) {
for (auto port : cell.second->ports) {
if (port.second.type == PORT_OUT) {
IdString clock_domain = ctx->getPortClock(cell.second.get(), port.first);
if (clock_domain != IdString()) {
- delay_t slack = delay_t(1.0e12 / ctx->target_freq); // TODO: clock constraints
+ delay_t slack = default_slack; // TODO: clock constraints
DelayInfo clkToQ;
if (ctx->getCellDelay(cell.second.get(), clock_domain, port.first, clkToQ))
slack -= clkToQ.maxDelay();
if (port.second.net)
- follow_net(ctx, port.second.net, 0, slack);
+ follow_net(ctx, port.second.net, 0, slack, update, min_slack, &current_path, crit_path);
}
}
}
}
- // Post-allocation check
+ return min_slack;
+}
+
+void assign_budget(Context *ctx, bool quiet)
+{
+ if (!quiet) {
+ log_break();
+ log_info("Annotating ports with timing budgets\n");
+ }
+
+ // Clear delays to a very high value first
+ delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
for (auto &net : ctx->nets) {
- for (auto user : net.second->users) {
- if (user.budget < 0)
- log_warning("port %s.%s, connected to net '%s', has negative "
- "timing budget of %fns\n",
- user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx),
- ctx->getDelayNS(user.budget));
- if (ctx->verbose)
- log_info("port %s.%s, connected to net '%s', has "
- "timing budget of %fns\n",
- user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx),
- ctx->getDelayNS(user.budget));
+ for (auto &usr : net.second->users) {
+ usr.budget = default_slack;
+ }
+ }
+
+ delay_t min_slack = walk_paths(ctx, true, nullptr);
+
+ if (!quiet || ctx->verbose) {
+ for (auto &net : ctx->nets) {
+ for (auto &user : net.second->users) {
+ // Post-update check
+ if (ctx->user_freq && user.budget < 0)
+ log_warning("port %s.%s, connected to net '%s', has negative "
+ "timing budget of %fns\n",
+ user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx),
+ ctx->getDelayNS(user.budget));
+ else if (ctx->verbose)
+ log_info("port %s.%s, connected to net '%s', has "
+ "timing budget of %fns\n",
+ user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx),
+ ctx->getDelayNS(user.budget));
+ }
}
}
- log_info("Checksum: 0x%08x\n", ctx->checksum());
+ // For slack redistribution, if user has not specified a frequency
+ // dynamically adjust the target frequency to be the currently
+ // achieved maximum
+ if (!ctx->user_freq && ctx->slack_redist_iter > 0) {
+ ctx->target_freq = 1e12 / (default_slack - min_slack);
+ /*if (ctx->verbose)*/
+ log_info("minimum slack for this assign = %d, target Fmax for next update = %.2f MHz\n", min_slack,
+ ctx->target_freq / 1e6);
+ }
+
+ if (!quiet)
+ log_info("Checksum: 0x%08x\n", ctx->checksum());
+}
+
+delay_t timing_analysis(Context *ctx, bool print_fmax, bool print_path)
+{
+ delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
+ PortRefList crit_path;
+ delay_t min_slack = walk_paths(ctx, false, &crit_path);
+ if (print_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;
+ auto last_port = ctx->getPortClock(front_driver.cell, front_driver.port);
+ for (auto sink : crit_path) {
+ auto sink_cell = sink->cell;
+ auto &port = sink_cell->ports.at(sink->port);
+ auto net = port.net;
+ auto &driver = net->driver;
+ auto driver_cell = driver.cell;
+ DelayInfo comb_delay;
+ ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
+ total += comb_delay.maxDelay();
+ log_info("%4d %4d Source %s.%s\n", comb_delay.maxDelay(), total, driver_cell->name.c_str(ctx),
+ driver.port.c_str(ctx));
+ auto net_delay = ctx->getNetinfoRouteDelay(net, *sink);
+ total += net_delay;
+ auto driver_loc = ctx->getBelLocation(driver_cell->bel);
+ auto sink_loc = ctx->getBelLocation(sink_cell->bel);
+ log_info("%4d %4d Net %s budget %d (%d,%d) -> (%d,%d)\n", net_delay, total, net->name.c_str(ctx),
+ 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));
+ last_port = sink->port;
+ }
+ log_break();
+ }
+ if (print_fmax)
+ log_info("estimated Fmax = %.2f MHz\n", 1e6 / (default_slack - min_slack));
+ return min_slack;
}
NEXTPNR_NAMESPACE_END
diff --git a/common/timing.h b/common/timing.h
index 025e4a76..d0159d5c 100644
--- a/common/timing.h
+++ b/common/timing.h
@@ -24,8 +24,12 @@
NEXTPNR_NAMESPACE_BEGIN
-// Assign "budget" values for all user ports in the design
-void assign_budget(Context *ctx);
+// Evenly redistribute the total path slack amongst all sinks on each path
+void assign_budget(Context *ctx, bool quiet = false);
+
+// Perform timing analysis and return the minimum path slack,
+// optionally, print out the fmax and critical path
+delay_t timing_analysis(Context *ctx, bool print_fmax = false, bool print_path = false);
NEXTPNR_NAMESPACE_END
diff --git a/ecp5/arch.cc b/ecp5/arch.cc
index 2b40e79a..5fc86cdb 100644
--- a/ecp5/arch.cc
+++ b/ecp5/arch.cc
@@ -413,6 +413,17 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
return 200 * (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;
+{
+ const auto &driver = net_info->driver;
+ auto driver_loc = getBelLocation(driver.cell->bel);
+ auto sink_loc = getBelLocation(sink.cell->bel);
+
+ return 200 * (abs(driver_loc.x - sink_loc.x) + abs(driver_loc.y - sink_loc.y));
+}
+
+delay_t getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t budget) const { return budget; }
+
// -----------------------------------------------------------------------
bool Arch::place() { return placer1(getCtx()); }
diff --git a/ecp5/arch.h b/ecp5/arch.h
index 2421428f..e4d67e0f 100644
--- a/ecp5/arch.h
+++ b/ecp5/arch.h
@@ -776,10 +776,12 @@ struct Arch : BaseCtx
// -------------------------------------------------
delay_t estimateDelay(WireId src, WireId dst) const;
+ delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const;
delay_t getDelayEpsilon() const { return 20; }
delay_t getRipupDelayPenalty() const { return 200; }
float getDelayNS(delay_t v) const { return v * 0.001; }
uint32_t getDelayChecksum(delay_t v) const { return v; }
+ delay_t getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t budget) const;
// -------------------------------------------------
diff --git a/ecp5/main.cc b/ecp5/main.cc
index 90096855..68660ced 100644
--- a/ecp5/main.cc
+++ b/ecp5/main.cc
@@ -169,8 +169,12 @@ int main(int argc, char *argv[])
if (!ctx->pack() && !ctx->force)
log_error("Packing design failed.\n");
- if (vm.count("freq"))
+ if (vm.count("freq")) {
ctx->target_freq = vm["freq"].as<double>() * 1e6;
+ ctx->user_freq = true;
+ } else {
+ log_warning("Target frequency not specified. Will optimise for max frequency.\n");
+ }
assign_budget(ctx.get());
ctx->check();
print_utilisation(ctx.get());
diff --git a/generic/arch.cc b/generic/arch.cc
index 892bb0fd..b1905e6f 100644
--- a/generic/arch.cc
+++ b/generic/arch.cc
@@ -403,6 +403,19 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
return (dx + dy) * grid_distance_to_delay;
}
+delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const;
+{
+ const auto &driver = net_info->driver;
+ auto driver_loc = getBelLocation(driver.cell->bel);
+ auto sink_loc = getBelLocation(sink.cell->bel);
+
+ int dx = abs(driver_loc.x - driver_loc.x);
+ int dy = abs(sink_loc.y - sink_locy);
+ return (dx + dy) * grid_distance_to_delay;
+}
+
+delay_t getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t budget) const { return budget; }
+
// ---------------------------------------------------------------
bool Arch::place() { return placer1(getCtx()); }
diff --git a/generic/arch.h b/generic/arch.h
index 9a0da75b..17d62ec3 100644
--- a/generic/arch.h
+++ b/generic/arch.h
@@ -194,10 +194,12 @@ struct Arch : BaseCtx
const std::vector<GroupId> &getGroupGroups(GroupId group) const;
delay_t estimateDelay(WireId src, WireId dst) const;
+ delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const;
delay_t getDelayEpsilon() const { return 0.01; }
delay_t getRipupDelayPenalty() const { return 1.0; }
float getDelayNS(delay_t v) const { return v; }
uint32_t getDelayChecksum(delay_t v) const { return 0; }
+ delay_t getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t budget) const;
bool pack() { return true; }
bool place();
diff --git a/generic/main.cc b/generic/main.cc
index d5a65102..3b8b3fe6 100644
--- a/generic/main.cc
+++ b/generic/main.cc
@@ -75,16 +75,18 @@ int main(int argc, char *argv[])
}
if (vm.count("help") || argc == 1) {
- std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
- "sha1 " GIT_COMMIT_HASH_STR ")\n";
+ std::cout << boost::filesystem::basename(argv[0])
+ << " -- Next Generation Place and Route (git "
+ "sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << "\n";
std::cout << options << "\n";
return argc != 1;
}
if (vm.count("version")) {
- std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
- "sha1 " GIT_COMMIT_HASH_STR ")\n";
+ std::cout << boost::filesystem::basename(argv[0])
+ << " -- Next Generation Place and Route (git "
+ "sha1 " GIT_COMMIT_HASH_STR ")\n";
return 1;
}
diff --git a/gui/designwidget.cc b/gui/designwidget.cc
index e8c05ef9..34e358ae 100644
--- a/gui/designwidget.cc
+++ b/gui/designwidget.cc
@@ -34,7 +34,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel
{
// Add tree view
treeView = new QTreeView();
- treeModel = new ContextTreeModel();
+ treeModel = new TreeModel::Model();
treeView->setModel(treeModel);
treeView->setContextMenuPolicy(Qt::CustomContextMenu);
treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
@@ -215,7 +215,11 @@ void DesignWidget::newContext(Context *ctx)
highlightSelected.clear();
this->ctx = ctx;
- treeModel->loadData(ctx);
+ {
+ std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
+ std::lock_guard<std::mutex> lock(ctx->mutex);
+ treeModel->loadContext(ctx);
+ }
updateTree();
}
@@ -223,9 +227,9 @@ void DesignWidget::updateTree()
{
clearProperties();
- QMap<ContextTreeItem *, int>::iterator i = highlightSelected.begin();
+ QMap<TreeModel::Item *, int>::iterator i = highlightSelected.begin();
while (i != highlightSelected.end()) {
- QMap<ContextTreeItem *, int>::iterator prev = i;
+ QMap<TreeModel::Item *, int>::iterator prev = i;
++i;
if (prev.key()->type() == ElementType::NET && ctx->nets.find(prev.key()->id()) == ctx->nets.end()) {
highlightSelected.erase(prev);
@@ -235,7 +239,11 @@ void DesignWidget::updateTree()
}
}
- treeModel->updateData(ctx);
+ {
+ std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
+ std::lock_guard<std::mutex> lock(ctx->mutex);
+ treeModel->updateCellsNets(ctx);
+ }
}
QtProperty *DesignWidget::addTopLevelProperty(const QString &id)
{
@@ -309,24 +317,33 @@ QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name)
void DesignWidget::onClickedBel(BelId bel, bool keep)
{
- ContextTreeItem *item = treeModel->nodeForIdType(ElementType::BEL, ctx->getBelName(bel).c_str(ctx));
- selectionModel->setCurrentIndex(treeModel->indexFromNode(item),
+ auto item = treeModel->nodeForIdType(ElementType::BEL, ctx->getBelName(bel));
+ if (!item)
+ return;
+
+ selectionModel->setCurrentIndex(treeModel->indexFromNode(*item),
keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep);
}
void DesignWidget::onClickedWire(WireId wire, bool keep)
{
- ContextTreeItem *item = treeModel->nodeForIdType(ElementType::WIRE, ctx->getWireName(wire).c_str(ctx));
- selectionModel->setCurrentIndex(treeModel->indexFromNode(item),
+ auto item = treeModel->nodeForIdType(ElementType::WIRE, ctx->getWireName(wire));
+ if (!item)
+ return;
+
+ selectionModel->setCurrentIndex(treeModel->indexFromNode(*item),
keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep);
}
void DesignWidget::onClickedPip(PipId pip, bool keep)
{
- ContextTreeItem *item = treeModel->nodeForIdType(ElementType::PIP, ctx->getPipName(pip).c_str(ctx));
- selectionModel->setCurrentIndex(treeModel->indexFromNode(item),
+ auto item = treeModel->nodeForIdType(ElementType::PIP, ctx->getPipName(pip));
+ if (!item)
+ return;
+
+ selectionModel->setCurrentIndex(treeModel->indexFromNode(*item),
keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep);
}
@@ -339,7 +356,7 @@ void DesignWidget::onSelectionChanged(const QItemSelection &, const QItemSelecti
if (selectionModel->selectedIndexes().size() > 1) {
std::vector<DecalXY> decals;
for (auto index : selectionModel->selectedIndexes()) {
- ContextTreeItem *item = treeModel->nodeFromIndex(index);
+ TreeModel::Item *item = treeModel->nodeFromIndex(index);
std::vector<DecalXY> d = getDecals(item->type(), item->id());
std::move(d.begin(), d.end(), std::back_inserter(decals));
}
@@ -349,7 +366,7 @@ void DesignWidget::onSelectionChanged(const QItemSelection &, const QItemSelecti
QModelIndex index = selectionModel->selectedIndexes().at(0);
if (!index.isValid())
return;
- ContextTreeItem *clickItem = treeModel->nodeFromIndex(index);
+ TreeModel::Item *clickItem = treeModel->nodeFromIndex(index);
ElementType type = clickItem->type();
if (type == ElementType::NONE)
@@ -597,7 +614,7 @@ std::vector<DecalXY> DesignWidget::getDecals(ElementType type, IdString value)
return decals;
}
-void DesignWidget::updateHighlightGroup(QList<ContextTreeItem *> items, int group)
+void DesignWidget::updateHighlightGroup(QList<TreeModel::Item *> items, int group)
{
const bool shouldClear = items.size() == 1;
for (auto item : items) {
@@ -622,7 +639,7 @@ void DesignWidget::updateHighlightGroup(QList<ContextTreeItem *> items, int grou
void DesignWidget::prepareMenuProperty(const QPoint &pos)
{
QTreeWidget *tree = propertyEditor->treeWidget();
- QList<ContextTreeItem *> items;
+ QList<TreeModel::Item *> items;
for (auto itemContextMenu : tree->selectedItems()) {
QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu);
if (!browserItem)
@@ -632,11 +649,14 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos)
if (type == ElementType::NONE)
continue;
IdString value = ctx->id(selectedProperty->valueText().toStdString());
- items.append(treeModel->nodeForIdType(type, value.c_str(ctx)));
+ auto node = treeModel->nodeForIdType(type, value);
+ if (!node)
+ continue;
+ items.append(*node);
}
int selectedIndex = -1;
if (items.size() == 1) {
- ContextTreeItem *item = items.at(0);
+ TreeModel::Item *item = items.at(0);
if (highlightSelected.contains(item))
selectedIndex = highlightSelected[item];
}
@@ -677,13 +697,13 @@ void DesignWidget::prepareMenuTree(const QPoint &pos)
if (selectionModel->selectedIndexes().size() == 0)
return;
- QList<ContextTreeItem *> items;
+ QList<TreeModel::Item *> items;
for (auto index : selectionModel->selectedIndexes()) {
- ContextTreeItem *item = treeModel->nodeFromIndex(index);
+ TreeModel::Item *item = treeModel->nodeFromIndex(index);
items.append(item);
}
if (items.size() == 1) {
- ContextTreeItem *item = items.at(0);
+ TreeModel::Item *item = items.at(0);
if (highlightSelected.contains(item))
selectedIndex = highlightSelected[item];
}
@@ -709,9 +729,9 @@ void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column)
{
QtProperty *selectedProperty = propertyEditor->itemToBrowserItem(item)->property();
ElementType type = getElementTypeByName(selectedProperty->propertyId());
- ContextTreeItem *it = treeModel->nodeForIdType(type, selectedProperty->valueText());
+ auto it = treeModel->nodeForIdType(type, ctx->id(selectedProperty->valueText().toStdString()));
if (it)
- selectionModel->setCurrentIndex(treeModel->indexFromNode(it), QItemSelectionModel::ClearAndSelect);
+ selectionModel->setCurrentIndex(treeModel->indexFromNode(*it), QItemSelectionModel::ClearAndSelect);
}
void DesignWidget::onDoubleClicked(const QModelIndex &index) { Q_EMIT zoomSelected(); }
@@ -723,6 +743,9 @@ void DesignWidget::onSearchInserted()
if (currentIndex >= currentSearchIndexes.size())
currentIndex = 0;
} else {
+ std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
+ std::lock_guard<std::mutex> lock(ctx->mutex);
+
currentSearch = searchEdit->text();
currentSearchIndexes = treeModel->search(searchEdit->text());
currentIndex = 0;
diff --git a/gui/designwidget.h b/gui/designwidget.h
index 535fd0c3..628586f4 100644
--- a/gui/designwidget.h
+++ b/gui/designwidget.h
@@ -51,7 +51,7 @@ class DesignWidget : public QWidget
void updateButtons();
void addToHistory(QModelIndex item);
std::vector<DecalXY> getDecals(ElementType type, IdString value);
- void updateHighlightGroup(QList<ContextTreeItem *> item, int group);
+ void updateHighlightGroup(QList<TreeModel::Item *> item, int group);
Q_SIGNALS:
void info(std::string text);
void selected(std::vector<DecalXY> decal, bool keep);
@@ -77,7 +77,7 @@ class DesignWidget : public QWidget
QTreeView *treeView;
QItemSelectionModel *selectionModel;
- ContextTreeModel *treeModel;
+ TreeModel::Model *treeModel;
QLineEdit *searchEdit;
QtVariantPropertyManager *variantManager;
QtVariantPropertyManager *readOnlyManager;
@@ -99,7 +99,7 @@ class DesignWidget : public QWidget
QAction *actionClear;
QColor highlightColors[8];
- QMap<ContextTreeItem *, int> highlightSelected;
+ QMap<TreeModel::Item *, int> highlightSelected;
QString currentSearch;
QList<QModelIndex> currentSearchIndexes;
diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc
index ed25a187..66739b28 100644
--- a/gui/fpgaviewwidget.cc
+++ b/gui/fpgaviewwidget.cc
@@ -323,14 +323,18 @@ void FPGAViewWidget::paintGL()
flags = rendererData_->flags;
}
- {
- QMutexLocker locker(&rendererArgsLock_);
- rendererArgs_->flags.clear();
- }
-
// Check flags passed through pipeline.
if (flags.zoomOutbound) {
- 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;
+ }
+ }
}
}
diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h
index c35821d9..a40a0153 100644
--- a/gui/fpgaviewwidget.h
+++ b/gui/fpgaviewwidget.h
@@ -251,8 +251,6 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
zoomOutbound = other.zoomOutbound;
return *this;
}
-
- void clear() { zoomOutbound = false; }
};
struct RendererArgs
diff --git a/gui/ice40/worker.cc b/gui/ice40/worker.cc
index fd46ecad..09093ec8 100644
--- a/gui/ice40/worker.cc
+++ b/gui/ice40/worker.cc
@@ -109,6 +109,7 @@ void Worker::budget(double freq)
Q_EMIT taskStarted();
try {
ctx->target_freq = freq;
+ assign_budget(ctx);
Q_EMIT budget_finish(true);
} catch (WorkerInterruptionRequested) {
Q_EMIT taskCanceled();
@@ -120,8 +121,6 @@ void Worker::place(bool timing_driven)
Q_EMIT taskStarted();
try {
ctx->timing_driven = timing_driven;
- log_info("Assigned budget %0.2f MHz", ctx->target_freq / 1e6);
- assign_budget(ctx);
Q_EMIT place_finished(ctx->place());
} catch (WorkerInterruptionRequested) {
Q_EMIT taskCanceled();
diff --git a/gui/quadtree.h b/gui/quadtree.h
index b3ebf81c..a6c38a85 100644
--- a/gui/quadtree.h
+++ b/gui/quadtree.h
@@ -50,7 +50,6 @@ template <typename CoordinateT, typename ElementT> class QuadTreeNode
{
}
-
BoundingBox() : x0_(pinf), y0_(pinf), x1_(ninf), y1_(ninf) {}
BoundingBox(const BoundingBox &other) : x0_(other.x0_), y0_(other.y0_), x1_(other.x1_), y1_(other.y1_) {}
@@ -83,7 +82,7 @@ template <typename CoordinateT, typename ElementT> class QuadTreeNode
CoordinateT y0() const { return y0_; }
CoordinateT x1() const { return x1_; }
CoordinateT y1() const { return y1_; }
-
+
void setX0(CoordinateT v) { x0_ = v; }
void setY0(CoordinateT v) { y0_ = v; }
void setX1(CoordinateT v) { x1_ = v; }
diff --git a/gui/treemodel.cc b/gui/treemodel.cc
index d42dc401..900d5101 100644
--- a/gui/treemodel.cc
+++ b/gui/treemodel.cc
@@ -2,6 +2,7 @@
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
+ * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -18,265 +19,249 @@
*/
#include "treemodel.h"
+#include "log.h"
NEXTPNR_NAMESPACE_BEGIN
-static bool contextTreeItemLessThan(const ContextTreeItem *v1, const ContextTreeItem *v2)
- {
- return v1->name() < v2->name();
- }
+namespace TreeModel {
-ContextTreeItem::ContextTreeItem() { parentNode = nullptr; }
-
-ContextTreeItem::ContextTreeItem(QString name)
- : parentNode(nullptr), itemId(IdString()), itemType(ElementType::NONE), itemName(name)
+// converts 'aa123bb432' -> ['aa', '123', 'bb', '432']
+std::vector<QString> IdStringList::alphaNumSplit(const QString &str)
{
-}
+ std::vector<QString> res;
+ QString current_part;
+
+ bool number = true;
+ for (const auto c : str) {
+ if (current_part.size() == 0 && res.size() == 0) {
+ current_part.push_back(c);
+ number = c.isNumber();
+ continue;
+ }
-ContextTreeItem::ContextTreeItem(IdString id, ElementType type, QString name)
- : parentNode(nullptr), itemId(id), itemType(type), itemName(name)
-{
-}
+ if (number != c.isNumber()) {
+ number = c.isNumber();
+ res.push_back(current_part);
+ current_part.clear();
+ }
-ContextTreeItem::~ContextTreeItem()
-{
- if (parentNode)
- parentNode->children.removeOne(this);
- qDeleteAll(children);
+ current_part.push_back(c);
+ }
+
+ res.push_back(current_part);
+
+ return res;
}
-void ContextTreeItem::addChild(ContextTreeItem *item)
+
+void IdStringList::updateElements(Context *ctx, std::vector<IdString> elements)
{
- item->parentNode = this;
- children.append(item);
+ bool changed = false;
+
+ // For any elements that are not yet in managed_, created them.
+ std::unordered_set<IdString> element_set;
+ for (auto elem : elements) {
+ element_set.insert(elem);
+ auto existing = managed_.find(elem);
+ if (existing == managed_.end()) {
+ auto item = new IdStringItem(ctx, elem, this, child_type_);
+ managed_.emplace(elem, std::unique_ptr<IdStringItem>(item));
+ changed = true;
+ }
+ }
+
+ // For any elements that are in managed_ but not in new, delete them.
+ for (auto &pair : managed_) {
+ if (element_set.count(pair.first) != 0) {
+ continue;
+ }
+ managed_.erase(pair.first);
+ changed = true;
+ }
+
+ // Return early if there are no changes.
+ if (!changed)
+ return;
+
+ // Rebuild children list.
+ children_.clear();
+ for (auto &pair : managed_) {
+ if (element_set.count(pair.first) != 0) {
+ children_.push_back(pair.second.get());
+ }
+ }
+
+ // Sort new children
+ qSort(children_.begin(), children_.end(), [&](const Item *a, const Item *b) {
+ auto parts_a = alphaNumSplit(a->name());
+ auto parts_b = alphaNumSplit(b->name());
+
+ // Short-circuit for different part count.
+ if (parts_a.size() != parts_b.size()) {
+ return parts_a.size() < parts_b.size();
+ }
+
+ for (size_t i = 0; i < parts_a.size(); i++) {
+ auto &part_a = parts_a.at(i);
+ auto &part_b = parts_b.at(i);
+
+ bool a_is_number, b_is_number;
+ int a_number = part_a.toInt(&a_is_number);
+ int b_number = part_b.toInt(&b_is_number);
+
+ // If both parts are numbers, compare numerically.
+ // If they're equal, continue to next part.
+ if (a_is_number && b_is_number) {
+ if (a_number != b_number) {
+ return a_number < b_number;
+ } else {
+ continue;
+ }
+ }
+
+ // For different alpha/nonalpha types, make numeric parts appear
+ // first.
+ if (a_is_number != b_is_number) {
+ return a_is_number;
+ }
+
+ // If both parts are numbers, compare lexically.
+ // If they're equal, continue to next part.
+ if (part_a == part_b) {
+ continue;
+ }
+ return part_a < part_b;
+ }
+
+ // Same string.
+ return true;
+ });
}
-void ContextTreeItem::sort()
+void IdStringList::search(QList<Item *> &results, QString text, int limit)
{
- for (auto item : children)
- if (item->count()>1) item->sort();
- qSort(children.begin(), children.end(), contextTreeItemLessThan);
+ for (const auto &child : children_) {
+ if (limit != -1 && results.size() > limit)
+ return;
+
+ if (child->name().contains(text))
+ results.push_back(child);
+ }
}
-ContextTreeModel::ContextTreeModel(QObject *parent) : QAbstractItemModel(parent) { root = new ContextTreeItem(); }
+Model::Model(QObject *parent) : QAbstractItemModel(parent), root_(new Item("Elements", nullptr)) {}
-ContextTreeModel::~ContextTreeModel() { delete root; }
+Model::~Model() {}
-void ContextTreeModel::loadData(Context *ctx)
+void Model::loadContext(Context *ctx)
{
if (!ctx)
return;
+ ctx_ = ctx;
beginResetModel();
- delete root;
- root = new ContextTreeItem();
-
- for (int i = 0; i < 6; i++)
- nameToItem[i].clear();
-
- IdString none;
-
- ContextTreeItem *bels_root = new ContextTreeItem("Bels");
- root->addChild(bels_root);
- QMap<QString, ContextTreeItem *> bel_items;
-
- // Add bels to tree
- for (auto bel : ctx->getBels()) {
- IdString id = ctx->getBelName(bel);
- QStringList items = QString(id.c_str(ctx)).split("/");
- QString name;
- ContextTreeItem *parent = bels_root;
- for (int i = 0; i < items.size(); i++) {
- if (!name.isEmpty())
- name += "/";
- name += items.at(i);
- if (!bel_items.contains(name)) {
- if (i == items.size() - 1) {
- ContextTreeItem *item = new ContextTreeItem(id, ElementType::BEL, items.at(i));
- parent->addChild(item);
- nameToItem[0].insert(name, item);
- } else {
- ContextTreeItem *item = new ContextTreeItem(none, ElementType::NONE, items.at(i));
- parent->addChild(item);
- bel_items.insert(name, item);
- }
- }
- parent = bel_items[name];
+ // 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);
}
- }
- bels_root->sort();
-
- ContextTreeItem *wire_root = new ContextTreeItem("Wires");
- root->addChild(wire_root);
- QMap<QString, ContextTreeItem *> wire_items;
-
- // Add wires to tree
- for (auto wire : ctx->getWires()) {
- auto id = ctx->getWireName(wire);
- QStringList items = QString(id.c_str(ctx)).split("/");
- QString name;
- ContextTreeItem *parent = wire_root;
- for (int i = 0; i < items.size(); i++) {
- if (!name.isEmpty())
- name += "/";
- name += items.at(i);
- if (!wire_items.contains(name)) {
- if (i == items.size() - 1) {
- ContextTreeItem *item = new ContextTreeItem(id, ElementType::WIRE, items.at(i));
- parent->addChild(item);
- nameToItem[1].insert(name, item);
- } else {
- ContextTreeItem *item = new ContextTreeItem(none, ElementType::NONE, items.at(i));
- parent->addChild(item);
- wire_items.insert(name, item);
- }
- }
- parent = wire_items[name];
+ 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);
}
- }
- wire_root->sort();
-
- ContextTreeItem *pip_root = new ContextTreeItem("Pips");
- root->addChild(pip_root);
- QMap<QString, ContextTreeItem *> pip_items;
-
- // Add pips to tree
-#ifndef ARCH_ECP5
- for (auto pip : ctx->getPips()) {
- auto id = ctx->getPipName(pip);
- QStringList items = QString(id.c_str(ctx)).split("/");
- QString name;
- ContextTreeItem *parent = pip_root;
- for (int i = 0; i < items.size(); i++) {
- if (!name.isEmpty())
- name += "/";
- name += items.at(i);
- if (!pip_items.contains(name)) {
- if (i == items.size() - 1) {
- ContextTreeItem *item = new ContextTreeItem(id, ElementType::PIP, items.at(i));
- parent->addChild(item);
- nameToItem[2].insert(name, item);
- } else {
- ContextTreeItem *item = new ContextTreeItem(none, ElementType::NONE, items.at(i));
- parent->addChild(item);
- pip_items.insert(name, item);
- }
- }
- parent = pip_items[name];
+ 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
- pip_root->sort();
- nets_root = new ContextTreeItem("Nets");
- root->addChild(nets_root);
-
- cells_root = new ContextTreeItem("Cells");
- root->addChild(cells_root);
+ 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));
endResetModel();
+
+ updateCellsNets(ctx);
}
-void ContextTreeModel::updateData(Context *ctx)
+void Model::updateCellsNets(Context *ctx)
{
if (!ctx)
return;
beginResetModel();
- //QModelIndex nets_index = indexFromNode(nets_root);
- // Remove nets not existing any more
- QMap<QString, ContextTreeItem *>::iterator i = nameToItem[3].begin();
- while (i != nameToItem[3].end()) {
- QMap<QString, ContextTreeItem *>::iterator prev = i;
- ++i;
- if (ctx->nets.find(ctx->id(prev.key().toStdString())) == ctx->nets.end()) {
- //int pos = prev.value()->parent()->indexOf(prev.value());
- //beginRemoveRows(nets_index, pos, pos);
- delete prev.value();
- nameToItem[3].erase(prev);
- //endRemoveRows();
- }
- }
- // Add nets to tree
- for (auto &item : ctx->nets) {
- auto id = item.first;
- QString name = QString(id.c_str(ctx));
- if (!nameToItem[3].contains(name)) {
- //beginInsertRows(nets_index, nets_root->count() + 1, nets_root->count() + 1);
- ContextTreeItem *newItem = new ContextTreeItem(id, ElementType::NET, name);
- nets_root->addChild(newItem);
- nameToItem[3].insert(name, newItem);
- //endInsertRows();
- }
+ std::vector<IdString> cells;
+ for (auto &pair : ctx->cells) {
+ cells.push_back(pair.first);
}
+ cell_root_->updateElements(ctx, cells);
- nets_root->sort();
-
- //QModelIndex cell_index = indexFromNode(cells_root);
- // Remove cells not existing any more
- i = nameToItem[4].begin();
- while (i != nameToItem[4].end()) {
- QMap<QString, ContextTreeItem *>::iterator prev = i;
- ++i;
- if (ctx->cells.find(ctx->id(prev.key().toStdString())) == ctx->cells.end()) {
- //int pos = prev.value()->parent()->indexOf(prev.value());
- //beginRemoveRows(cell_index, pos, pos);
- delete prev.value();
- nameToItem[4].erase(prev);
- //endRemoveRows();
- }
+ std::vector<IdString> nets;
+ for (auto &pair : ctx->nets) {
+ nets.push_back(pair.first);
}
- // Add cells to tree
- for (auto &item : ctx->cells) {
- auto id = item.first;
- QString name = QString(id.c_str(ctx));
- if (!nameToItem[4].contains(name)) {
- //beginInsertRows(cell_index, cells_root->count() + 1, cells_root->count() + 1);
- ContextTreeItem *newItem = new ContextTreeItem(id, ElementType::CELL, name);
- cells_root->addChild(newItem);
- nameToItem[4].insert(name, newItem);
- //endInsertRows();
- }
- }
-
- cells_root->sort();
+ net_root_->updateElements(ctx, nets);
endResetModel();
}
-int ContextTreeModel::rowCount(const QModelIndex &parent) const { return nodeFromIndex(parent)->count(); }
+int Model::rowCount(const QModelIndex &parent) const { return nodeFromIndex(parent)->count(); }
-int ContextTreeModel::columnCount(const QModelIndex &parent) const { return 1; }
+int Model::columnCount(const QModelIndex &parent) const { return 1; }
-QModelIndex ContextTreeModel::index(int row, int column, const QModelIndex &parent) const
+QModelIndex Model::index(int row, int column, const QModelIndex &parent) const
{
- ContextTreeItem *node = nodeFromIndex(parent);
+ Item *node = nodeFromIndex(parent);
if (row >= node->count())
return QModelIndex();
- return createIndex(row, column, node->at(row));
+
+ return createIndex(row, column, node->child(row));
}
-QModelIndex ContextTreeModel::parent(const QModelIndex &child) const
+QModelIndex Model::parent(const QModelIndex &child) const
{
- ContextTreeItem *parent = nodeFromIndex(child)->parent();
- if (parent == root)
+ Item *parent = nodeFromIndex(child)->parent();
+ if (parent == root_.get())
return QModelIndex();
- ContextTreeItem *node = parent->parent();
+ Item *node = parent->parent();
return createIndex(node->indexOf(parent), 0, parent);
}
-QVariant ContextTreeModel::data(const QModelIndex &index, int role) const
+QVariant Model::data(const QModelIndex &index, int role) const
{
if (index.column() != 0)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
- ContextTreeItem *node = nodeFromIndex(index);
+ Item *node = nodeFromIndex(index);
return node->name();
}
-QVariant ContextTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(section);
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
@@ -285,62 +270,49 @@ QVariant ContextTreeModel::headerData(int section, Qt::Orientation orientation,
return QVariant();
}
-ContextTreeItem *ContextTreeModel::nodeFromIndex(const QModelIndex &idx) const
+Item *Model::nodeFromIndex(const QModelIndex &idx) const
{
if (idx.isValid())
- return (ContextTreeItem *)idx.internalPointer();
- return root;
+ return (Item *)idx.internalPointer();
+ return root_.get();
}
-static int getElementIndex(ElementType type)
+Qt::ItemFlags Model::flags(const QModelIndex &index) const
{
- 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;
- return -1;
+ Item *node = nodeFromIndex(index);
+ return Qt::ItemIsEnabled | (node->type() != ElementType::NONE ? Qt::ItemIsSelectable : Qt::NoItemFlags);
}
-ContextTreeItem *ContextTreeModel::nodeForIdType(const ElementType type, const QString name) const
+void Model::fetchMore(const QModelIndex &parent)
{
- int index = getElementIndex(type);
- if (type != ElementType::NONE && nameToItem[index].contains(name))
- return nameToItem[index].value(name);
- return nullptr;
-}
+ if (ctx_ == nullptr)
+ return;
-QModelIndex ContextTreeModel::indexFromNode(ContextTreeItem *node)
-{
- ContextTreeItem *parent = node->parent();
- if (parent == root)
- return QModelIndex();
- return createIndex(parent->indexOf(node), 0, node);
-}
+ std::lock_guard<std::mutex> lock_ui(ctx_->ui_mutex);
+ std::lock_guard<std::mutex> lock(ctx_->mutex);
-Qt::ItemFlags ContextTreeModel::flags(const QModelIndex &index) const
-{
- ContextTreeItem *node = nodeFromIndex(index);
- return Qt::ItemIsEnabled | (node->type() != ElementType::NONE ? Qt::ItemIsSelectable : Qt::NoItemFlags);
+ nodeFromIndex(parent)->fetchMore();
}
-QList<QModelIndex> ContextTreeModel::search(QString text)
+bool Model::canFetchMore(const QModelIndex &parent) const { return nodeFromIndex(parent)->canFetchMore(); }
+
+QList<QModelIndex> Model::search(QString text)
{
- QList<QModelIndex> list;
- for (int i = 0; i < 6; i++) {
- for (auto key : nameToItem[i].keys()) {
- if (key.contains(text, Qt::CaseInsensitive)) {
- list.append(indexFromNode(nameToItem[i].value(key)));
- if (list.count() > 500)
- break; // limit to 500 results
- }
- }
+ 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);
+
+ QList<QModelIndex> res;
+ for (auto i : list) {
+ res.push_back(indexFromNode(i));
}
- return list;
+ return res;
}
+
+}; // namespace TreeModel
+
NEXTPNR_NAMESPACE_END
diff --git a/gui/treemodel.h b/gui/treemodel.h
index c14efa90..4c3f64c3 100644
--- a/gui/treemodel.h
+++ b/gui/treemodel.h
@@ -2,6 +2,7 @@
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
+ * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +22,8 @@
#define TREEMODEL_H
#include <QAbstractItemModel>
+#include <boost/optional.hpp>
+
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
@@ -36,43 +39,350 @@ enum class ElementType
GROUP
};
-class ContextTreeItem
+namespace TreeModel {
+
+// Item is a leaf or non-leaf item in the TreeModel hierarchy. It does not
+// manage any memory.
+// It has a list of children, and when created it registers itself as a child
+// of its parent.
+// It has some PNR-specific members, like type (if any), idstring (if ay).
+// They should be overwritten by deriving classes to make them relate to an
+// object somewhere in the arch universe.
+// It also has provisions for lazy loading of data, via the canFetchMore and
+// fetchMore methods.
+class Item
+{
+ protected:
+ // Human-friendly name of this item.
+ QString name_;
+ // Parent or nullptr if root.
+ Item *parent_;
+ // Children that are loaded into memory.
+ QList<Item *> children_;
+
+ void addChild(Item *child) { children_.append(child); }
+
+ public:
+ Item(QString name, Item *parent) : name_(name), parent_(parent)
+ {
+ // Register in parent if exists.
+ if (parent_ != nullptr) {
+ parent_->addChild(this);
+ }
+ };
+
+ // Number of children.
+ int count() const { return children_.count(); }
+
+ // Name getter.
+ QString name() const { return name_; }
+
+ // Child getter.
+ Item *child(int index) { return children_.at(index); }
+
+ // Parent getter.
+ const Item *parent() const { return parent_; }
+ Item *parent() { return parent_; }
+
+ // indexOf gets index of child in children array.
+ int indexOf(const Item *child) const
+ {
+ // Dropping the const for indexOf to work.
+ return children_.indexOf((Item *)child, 0);
+ }
+ int indexOf(Item *child) { return children_.indexOf(child, 0); }
+
+ // Arch id and type that correspond to this element.
+ virtual IdString id() const { return IdString(); }
+ virtual ElementType type() const { return ElementType::NONE; }
+
+ // Lazy loading methods.
+ virtual bool canFetchMore() const { return false; }
+ virtual void fetchMore() {}
+
+ ~Item() {}
+};
+
+// IdString is an Item that corresponds to a real element in Arch.
+class IdStringItem : public Item
+{
+ private:
+ IdString id_;
+ ElementType type_;
+
+ public:
+ IdStringItem(Context *ctx, IdString str, Item *parent, ElementType type)
+ : Item(QString(str.c_str(ctx)), parent), id_(str), type_(type)
+ {
+ }
+
+ virtual IdString id() const override { return id_; }
+
+ virtual ElementType type() const override { return type_; }
+};
+
+// IdString list is a static list of IdStrings which can be set/updates from
+// a vector of IdStrings. It will render each IdStrings as a child, with the
+// list sorted in a smart way.
+class IdStringList : public Item
+{
+ private:
+ // Children that we manage the memory for, stored for quick lookup from
+ // IdString to child.
+ std::unordered_map<IdString, std::unique_ptr<IdStringItem>> managed_;
+ // Type of children that the list creates.
+ ElementType child_type_;
+
+ 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) {}
+
+ // 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(); }
+
+ // (Re-)create children from a list of IdStrings.
+ void updateElements(Context *ctx, std::vector<IdString> elements);
+
+ // Find children that contain the given text.
+ void search(QList<Item *> &results, QString text, int limit);
+};
+
+// ElementList is a dynamic list of ElementT (BelId,WireId,...) that are
+// automatically generated based on an overall map of elements.
+// ElementList is emitted from ElementXYRoot, and contains the actual
+// Bels/Wires/Pips underneath it.
+template <typename ElementT> class ElementList : public Item
+{
+ public:
+ // A map from tile (X,Y) to list of ElementTs in that tile.
+ using ElementMap = std::map<std::pair<int, int>, std::vector<ElementT>>;
+ // A method that converts an ElementT to an IdString.
+ using ElementGetter = std::function<IdString(Context *, ElementT)>;
+
+ private:
+ Context *ctx_;
+ // ElementMap given to use by our constructor.
+ const ElementMap *map_;
+ // The X, Y that this list handles.
+ int x_, y_;
+ ElementGetter getter_;
+ // Children that we manage the memory for, stored for quick lookup from
+ // IdString to child.
+ std::unordered_map<IdString, std::unique_ptr<Item>> managed_;
+ // Type of children that he list creates.
+ ElementType child_type_;
+
+ // Gets elements that this list should create from the map. This pointer is
+ // short-lived (as it will change when the map mutates.
+ const std::vector<ElementT> *elements() const { return &map_->at(std::make_pair(x_, y_)); }
+
+ public:
+ ElementList(Context *ctx, QString name, Item *parent, ElementMap *map, int x, int y, ElementGetter getter,
+ ElementType type)
+ : Item(name, parent), ctx_(ctx), map_(map), x_(x), y_(y), getter_(getter), child_type_(type)
+ {
+ }
+
+ // Lazy loading of elements.
+
+ virtual bool canFetchMore() const override { return (size_t)children_.size() < elements()->size(); }
+
+ void fetchMore(int count)
+ {
+ size_t start = children_.size();
+ size_t end = std::min(start + count, elements()->size());
+ for (size_t i = start; i < end; i++) {
+ auto idstring = getter_(ctx_, elements()->at(i));
+ QString name(idstring.c_str(ctx_));
+
+ // Remove X.../Y.../ prefix
+ QString prefix = QString("X%1/Y%2/").arg(x_).arg(y_);
+ if (name.startsWith(prefix))
+ name.remove(0, prefix.size());
+
+ auto item = new IdStringItem(ctx_, idstring, this, child_type_);
+ managed_[idstring] = std::move(std::unique_ptr<Item>(item));
+ }
+ }
+
+ virtual void fetchMore() override { fetchMore(100); }
+
+ // getById finds a child for the given IdString.
+ boost::optional<Item *> getById(IdString id)
+ {
+ // Search requires us to load all our elements...
+ while (canFetchMore())
+ fetchMore();
+
+ auto res = managed_.find(id);
+ if (res != managed_.end()) {
+ return res->second.get();
+ }
+ return boost::none;
+ }
+
+ // Find children that contain the given text.
+ void search(QList<Item *> &results, QString text, int limit)
+ {
+ // Last chance to bail out from loading entire tree into memory.
+ if (limit != -1 && results.size() > limit)
+ return;
+
+ // Search requires us to load all our elements...
+ while (canFetchMore())
+ fetchMore();
+
+ for (const auto &child : children_) {
+ if (limit != -1 && results.size() > limit)
+ return;
+ if (child->name().contains(text))
+ results.push_back(child);
+ }
+ }
+};
+
+// ElementXYRoot is the root of an ElementT multi-level lazy loading list.
+// It can take any of {BelId,WireId,PipId} and create a tree that
+// hierarchizes them by X and Y tile positions, when given a map from X,Y to
+// list of ElementTs in that tile.
+template <typename ElementT> class ElementXYRoot : public Item
{
public:
- ContextTreeItem();
- ContextTreeItem(QString name);
- ContextTreeItem(IdString id, ElementType type, QString name);
- ~ContextTreeItem();
-
- void addChild(ContextTreeItem *item);
- int indexOf(ContextTreeItem *n) const { return children.indexOf(n); }
- ContextTreeItem *at(int idx) const { return children.at(idx); }
- int count() const { return children.count(); }
- ContextTreeItem *parent() const { return parentNode; }
- IdString id() const { return itemId; }
- ElementType type() const { return itemType; }
- QString name() const { return itemName; }
- void sort();
+ // A map from tile (X,Y) to list of ElementTs in that tile.
+ using ElementMap = std::map<std::pair<int, int>, std::vector<ElementT>>;
+ // A method that converts an ElementT to an IdString.
+ using ElementGetter = std::function<IdString(Context *, ElementT)>;
+
private:
- ContextTreeItem *parentNode;
- QList<ContextTreeItem *> children;
- IdString itemId;
- ElementType itemType;
- QString itemName;
+ Context *ctx_;
+ // X-index children that we manage the memory for.
+ std::vector<std::unique_ptr<Item>> managed_labels_;
+ // Y-index children (ElementLists) that we manage the memory for.
+ std::vector<std::unique_ptr<ElementList<ElementT>>> managed_lists_;
+ // Source of truth for elements to display.
+ ElementMap map_;
+ ElementGetter getter_;
+ // Type of children that he list creates in X->Y->...
+ 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)
+ {
+ // Create all X and Y label Items/ElementLists.
+
+ // Y coordinates at which an element exists for a given X - taken out
+ // of loop to limit heap allocation/deallocation.
+ std::vector<int> y_present;
+
+ for (int i = 0; i < ctx->getGridDimX(); i++) {
+ y_present.clear();
+ // First find all the elements in all Y coordinates in this X.
+ for (int j = 0; j < ctx->getGridDimY(); j++) {
+ if (map_.count(std::make_pair(i, j)) == 0)
+ continue;
+ y_present.push_back(j);
+ }
+ // No elements in any X coordinate? Do not add X tree item.
+ if (y_present.size() == 0)
+ continue;
+
+ // Create X list Item.
+ auto item = new Item(QString("X%1").arg(i), this);
+ managed_labels_.push_back(std::move(std::unique_ptr<Item>(item)));
+
+ for (auto j : y_present) {
+ // Create Y list ElementList.
+ auto item2 =
+ new ElementList<ElementT>(ctx_, QString("Y%1").arg(j), item, &map_, i, j, getter_, child_type_);
+ // Pre-populate list with one element, other Qt will never ask for more.
+ item2->fetchMore(1);
+ managed_lists_.push_back(std::move(std::unique_ptr<ElementList<ElementT>>(item2)));
+ }
+ }
+ }
+
+ // getById finds a child for the given IdString.
+ boost::optional<Item *> getById(IdString id)
+ {
+ // For now, scan linearly all ElementLists.
+ // TODO(q3k) fix this once we have tree API from arch
+ for (auto &l : managed_lists_) {
+ auto res = l->getById(id);
+ if (res) {
+ return res;
+ }
+ }
+ return boost::none;
+ }
+
+ // Find children that contain the given text.
+ void search(QList<Item *> &results, QString text, int limit)
+ {
+ for (auto &l : managed_lists_) {
+ if (limit != -1 && results.size() > limit)
+ return;
+ l->search(results, text, limit);
+ }
+ }
};
-class ContextTreeModel : public QAbstractItemModel
+class Model : public QAbstractItemModel
{
+ private:
+ Context *ctx_ = nullptr;
+
public:
- ContextTreeModel(QObject *parent = nullptr);
- ~ContextTreeModel();
-
- void loadData(Context *ctx);
- void updateData(Context *ctx);
- ContextTreeItem *nodeFromIndex(const QModelIndex &idx) const;
- QModelIndex indexFromNode(ContextTreeItem *node);
- ContextTreeItem *nodeForIdType(const ElementType type, const QString name) const;
+ 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);
+ Item *nodeFromIndex(const QModelIndex &idx) const;
+ QModelIndex indexFromNode(Item *node)
+ {
+ const Item *parent = node->parent();
+ if (parent == nullptr)
+ return QModelIndex();
+
+ return createIndex(parent->indexOf(node), 0, node);
+ }
+
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;
+ }
+ }
+
// Override QAbstractItemModel methods
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
@@ -81,14 +391,21 @@ class ContextTreeModel : public QAbstractItemModel
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
+ void fetchMore(const QModelIndex &parent) Q_DECL_OVERRIDE;
+ bool canFetchMore(const QModelIndex &parent) const Q_DECL_OVERRIDE;
private:
- ContextTreeItem *root;
- QMap<QString, ContextTreeItem *> nameToItem[6];
- ContextTreeItem *nets_root;
- ContextTreeItem *cells_root;
+ // 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
+
NEXTPNR_NAMESPACE_END
#endif // TREEMODEL_H
diff --git a/ice40/arch.cc b/ice40/arch.cc
index f3004155..5545ddf4 100644
--- a/ice40/arch.cc
+++ b/ice40/arch.cc
@@ -317,15 +317,15 @@ PortType Arch::getBelPinType(BelId bel, PortPin pin) const
return PortType(bel_wires[i].type);
}
} else {
- int b = 0, e = num_bel_wires-1;
+ int b = 0, e = num_bel_wires - 1;
while (b <= e) {
- int i = (b+e) / 2;
+ int i = (b + e) / 2;
if (bel_wires[i].port == pin)
return PortType(bel_wires[i].type);
if (bel_wires[i].port > pin)
- e = i-1;
+ e = i - 1;
else
- b = i+1;
+ b = i + 1;
}
}
@@ -349,17 +349,17 @@ WireId Arch::getBelPinWire(BelId bel, PortPin pin) const
}
}
} else {
- int b = 0, e = num_bel_wires-1;
+ int b = 0, e = num_bel_wires - 1;
while (b <= e) {
- int i = (b+e) / 2;
+ int i = (b + e) / 2;
if (bel_wires[i].port == pin) {
ret.index = bel_wires[i].wire_index;
break;
}
if (bel_wires[i].port > pin)
- e = i-1;
+ e = i - 1;
else
- b = i+1;
+ b = i + 1;
}
}
@@ -613,14 +613,50 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
int xd = x2 - x1, yd = y2 - y1;
int xscale = 120, yscale = 120, offset = 0;
+ return xscale * abs(xd) + yscale * abs(yd) + offset;
+}
+
+delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
+{
+ const auto &driver = net_info->driver;
+ auto driver_loc = getBelLocation(driver.cell->bel);
+ auto sink_loc = getBelLocation(sink.cell->bel);
+
+ if (driver.port == id_cout) {
+ if (driver_loc.y == sink_loc.y)
+ return 0;
+ return 250;
+ }
+
+ int xd = sink_loc.x - driver_loc.x, yd = sink_loc.y - driver_loc.y;
+ int xscale = 120, yscale = 120, offset = 0;
+
// if (chip_info->wire_data[src.index].type == WIRE_TYPE_SP4_VERT) {
// yd = yd < -4 ? yd + 4 : (yd < 0 ? 0 : yd);
// offset = 500;
// }
+ if (driver.port == id_o)
+ offset += 330;
+ if (sink.port == id_i0 || sink.port == id_i1 || sink.port == id_i2 || sink.port == id_i3)
+ offset += 260;
+
return xscale * abs(xd) + yscale * abs(yd) + offset;
}
+delay_t Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t budget) const
+{
+ const auto &driver = net_info->driver;
+ if (driver.port == id_cout) {
+ auto driver_loc = getBelLocation(driver.cell->bel);
+ auto sink_loc = getBelLocation(sink.cell->bel);
+ if (driver_loc.y == sink_loc.y)
+ return 0;
+ return 250;
+ }
+ return budget;
+}
+
// -----------------------------------------------------------------------
bool Arch::place() { return placer1(getCtx()); }
diff --git a/ice40/arch.h b/ice40/arch.h
index 7efa733c..07a8070a 100644
--- a/ice40/arch.h
+++ b/ice40/arch.h
@@ -531,9 +531,9 @@ struct Arch : BaseCtx
DelayInfo delay;
NPNR_ASSERT(wire != WireId());
if (fast_part)
- delay.delay = chip_info->wire_data[wire.index].fast_delay;
+ delay.delay = chip_info->wire_data[wire.index].fast_delay;
else
- delay.delay = chip_info->wire_data[wire.index].slow_delay;
+ delay.delay = chip_info->wire_data[wire.index].slow_delay;
return delay;
}
@@ -697,10 +697,12 @@ struct Arch : BaseCtx
// -------------------------------------------------
delay_t estimateDelay(WireId src, WireId dst) const;
+ delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const;
delay_t getDelayEpsilon() const { return 20; }
delay_t getRipupDelayPenalty() const { return 200; }
float getDelayNS(delay_t v) const { return v * 0.001; }
uint32_t getDelayChecksum(delay_t v) const { return v; }
+ delay_t getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t budget) const;
// -------------------------------------------------
diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc
index af5febce..d0d6b205 100644
--- a/ice40/bitstream.cc
+++ b/ice40/bitstream.cc
@@ -425,6 +425,10 @@ void write_asc(const Context *ctx, std::ostream &out)
{"A_SIGNED", 1},
{"B_SIGNED", 1}};
configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false, std::string("IpConfig."));
+ } else if (cell.second->type == ctx->id("ICESTORM_HFOSC")) {
+ const std::vector<std::pair<std::string, int>> hfosc_params = {{"CLKHF_DIV", 2}, {"TRIM_EN", 1}};
+ configure_extra_cell(config, ctx, cell.second.get(), hfosc_params, true, std::string("IpConfig."));
+
} else if (cell.second->type == ctx->id("ICESTORM_PLL")) {
const std::vector<std::pair<std::string, int>> pll_params = {{"DELAY_ADJMODE_FB", 1},
{"DELAY_ADJMODE_REL", 1},
@@ -566,8 +570,9 @@ void write_asc(const Context *ctx, std::ostream &out)
set_config(ti, config.at(y).at(x),
"Cascade.IPCON_LC0" + std::to_string(lc_idx) + "_inmux02_5", true);
else
- set_config(ti, config.at(y).at(x), "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) +
- "_LC0" + std::to_string(lc_idx) + "_inmux02_5",
+ set_config(ti, config.at(y).at(x),
+ "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) + "_LC0" +
+ std::to_string(lc_idx) + "_inmux02_5",
true);
}
}
diff --git a/ice40/cells.cc b/ice40/cells.cc
index 610bf85e..5bdc7990 100644
--- a/ice40/cells.cc
+++ b/ice40/cells.cc
@@ -113,8 +113,8 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
add_port(ctx, new_cell.get(), "CLKLF", PORT_OUT);
add_port(ctx, new_cell.get(), "CLKLF_FABRIC", PORT_OUT);
} else if (type == ctx->id("ICESTORM_HFOSC")) {
- new_cell->params[ctx->id("CLKHF_DIV")] = "0";
- new_cell->params[ctx->id("TRIM_EN")] = "0";
+ new_cell->params[ctx->id("CLKHF_DIV")] = "0b00";
+ new_cell->params[ctx->id("TRIM_EN")] = "0b0";
add_port(ctx, new_cell.get(), "CLKHFEN", PORT_IN);
add_port(ctx, new_cell.get(), "CLKHFPU", PORT_IN);
diff --git a/ice40/family.cmake b/ice40/family.cmake
index 02d4b4d8..4f575b15 100644
--- a/ice40/family.cmake
+++ b/ice40/family.cmake
@@ -21,13 +21,13 @@ if (MSVC)
foreach (dev ${devices})
if (dev EQUAL "5k")
set(OPT_FAST "")
- set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings-up5k.txt)
+ set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings_up5k.txt)
elseif(dev EQUAL "384")
set(OPT_FAST "")
- set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings-lp384.txt)
+ set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings_lp384.txt)
else()
- set(OPT_FAST --fast ${ICEBOX_ROOT}/timings-hx${dev}.txt)
- set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings-lp${dev}.txt)
+ set(OPT_FAST --fast ${ICEBOX_ROOT}/timings_hx${dev}.txt)
+ set(OPT_SLOW --slow ${ICEBOX_ROOT}/timings_lp${dev}.txt)
endif()
set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt)
set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.bba)
diff --git a/ice40/main.cc b/ice40/main.cc
index 32815b26..6e8385e6 100644
--- a/ice40/main.cc
+++ b/ice40/main.cc
@@ -105,6 +105,8 @@ int main(int argc, char *argv[])
options.add_options()("asc", po::value<std::string>(), "asc bitstream file to write");
options.add_options()("read", po::value<std::string>(), "asc bitstream file to read");
options.add_options()("seed", po::value<int>(), "seed value for random number generator");
+ options.add_options()("slack_redist_iter", po::value<int>(),
+ "number of iterations between slack redistribution");
options.add_options()("version,V", "show version");
options.add_options()("tmfuzz", "run path delay estimate fuzzer");
options.add_options()("test", "check architecture database integrity");
@@ -144,16 +146,18 @@ int main(int argc, char *argv[])
#endif
if (vm.count("help") || argc == 1) {
help:
- std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
- "sha1 " GIT_COMMIT_HASH_STR ")\n";
+ std::cout << boost::filesystem::basename(argv[0])
+ << " -- Next Generation Place and Route (git "
+ "sha1 " GIT_COMMIT_HASH_STR ")\n";
std::cout << "\n";
std::cout << options << "\n";
return argc != 1;
}
if (vm.count("version")) {
- std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git "
- "sha1 " GIT_COMMIT_HASH_STR ")\n";
+ std::cout << boost::filesystem::basename(argv[0])
+ << " -- Next Generation Place and Route (git "
+ "sha1 " GIT_COMMIT_HASH_STR ")\n";
return 1;
}
@@ -302,6 +306,10 @@ int main(int argc, char *argv[])
ctx->rngseed(vm["seed"].as<int>());
}
+ if (vm.count("slack_redist_iter")) {
+ ctx->slack_redist_iter = vm["slack_redist_iter"].as<int>();
+ }
+
if (vm.count("svg")) {
std::cout << "<svg xmlns=\"http://www.w3.org/2000/svg\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n";
@@ -363,8 +371,15 @@ int main(int argc, char *argv[])
}
}
- if (vm.count("freq"))
+ if (vm.count("freq")) {
ctx->target_freq = vm["freq"].as<double>() * 1e6;
+ ctx->user_freq = true;
+ } else {
+#ifndef NO_GUI
+ if (!vm.count("gui"))
+#endif
+ log_warning("Target frequency not specified. Will optimise for max frequency.\n");
+ }
ctx->timing_driven = true;
if (vm.count("no-tmdriv"))
@@ -407,7 +422,6 @@ int main(int argc, char *argv[])
if (!ctx->pack() && !ctx->force)
log_error("Packing design failed.\n");
- assign_budget(ctx.get());
ctx->check();
print_utilisation(ctx.get());
if (!vm.count("pack-only")) {
diff --git a/ice40/pack.cc b/ice40/pack.cc
index 8182eb70..08f66369 100644
--- a/ice40/pack.cc
+++ b/ice40/pack.cc
@@ -607,12 +607,25 @@ static void pack_special(Context *ctx)
packed_cells.insert(ci->name);
replace_port(ci, ctx->id("CLKLFEN"), packed.get(), ctx->id("CLKLFEN"));
replace_port(ci, ctx->id("CLKLFPU"), packed.get(), ctx->id("CLKLFPU"));
- if (bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))) {
+ if (/*bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))*/ true) { // FIXME
replace_port(ci, ctx->id("CLKLF"), packed.get(), ctx->id("CLKLF_FABRIC"));
} else {
replace_port(ci, ctx->id("CLKLF"), packed.get(), ctx->id("CLKLF"));
}
new_cells.push_back(std::move(packed));
+ } else if (is_sb_hfosc(ctx, ci)) {
+ std::unique_ptr<CellInfo> packed =
+ create_ice_cell(ctx, ctx->id("ICESTORM_HFOSC"), ci->name.str(ctx) + "_OSC");
+ packed_cells.insert(ci->name);
+ packed->params[ctx->id("CLKHF_DIV")] = str_or_default(ci->params, ctx->id("CLKHF_DIV"), "0b00");
+ replace_port(ci, ctx->id("CLKHFEN"), packed.get(), ctx->id("CLKHFEN"));
+ replace_port(ci, ctx->id("CLKHFPU"), packed.get(), ctx->id("CLKHFPU"));
+ if (/*bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))*/ true) { // FIXME
+ replace_port(ci, ctx->id("CLKHF"), packed.get(), ctx->id("CLKHF_FABRIC"));
+ } else {
+ replace_port(ci, ctx->id("CLKHF"), packed.get(), ctx->id("CLKHF"));
+ }
+ new_cells.push_back(std::move(packed));
} else if (is_sb_spram(ctx, ci)) {
std::unique_ptr<CellInfo> packed =
create_ice_cell(ctx, ctx->id("ICESTORM_SPRAM"), ci->name.str(ctx) + "_RAM");
@@ -662,12 +675,12 @@ static void pack_special(Context *ctx)
auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")];
packed->params[ctx->id("FEEDBACK_PATH")] =
- feedback_path == "DELAY" ? "0" : feedback_path == "SIMPLE"
- ? "1"
- : feedback_path == "PHASE_AND_DELAY"
- ? "2"
- : feedback_path == "EXTERNAL" ? "6"
- : feedback_path;
+ feedback_path == "DELAY"
+ ? "0"
+ : feedback_path == "SIMPLE" ? "1"
+ : feedback_path == "PHASE_AND_DELAY"
+ ? "2"
+ : feedback_path == "EXTERNAL" ? "6" : feedback_path;
packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci));
NetInfo *pad_packagepin_net = nullptr;
diff --git a/ice40/place_legaliser.cc b/ice40/place_legaliser.cc
index 0d14fb35..a498e5f4 100644
--- a/ice40/place_legaliser.cc
+++ b/ice40/place_legaliser.cc
@@ -164,8 +164,6 @@ class PlacementLegaliser
ctx->assignArchInfo();
-
-
return legalised_carries && replaced_cells;
}
diff --git a/json/jsonparse.cc b/json/jsonparse.cc
index 450556e8..a0a4e8d0 100644
--- a/json/jsonparse.cc
+++ b/json/jsonparse.cc
@@ -462,9 +462,9 @@ void json_import_ports(Context *ctx, const string &modname, const std::vector<Id
ground_net(ctx, net.get());
log_info(" Floating wire node value, "
- "\'%s\' of port \'%s\' "
- "in cell \'%s\' of module \'%s\'\n, converted to zero driver",
- wire_node->data_string.c_str(), port_name.c_str(), obj_name.c_str(), modname.c_str());
+ "\'%s\' of port \'%s\' "
+ "in cell \'%s\' of module \'%s\'\n, converted to zero driver",
+ wire_node->data_string.c_str(), port_name.c_str(), obj_name.c_str(), modname.c_str());
} else
log_error(" Unknown fixed type wire node "
diff --git a/tests/gui/quadtree.cc b/tests/gui/quadtree.cc
index 083a0057..6711e906 100644
--- a/tests/gui/quadtree.cc
+++ b/tests/gui/quadtree.cc
@@ -19,6 +19,7 @@
#include "gtest/gtest.h"
#include "nextpnr.h"
+
#include "quadtree.h"
USING_NEXTPNR_NAMESPACE
@@ -28,18 +29,12 @@ using QT = QuadTree<int, int>;
class QuadTreeTest : public ::testing::Test
{
protected:
- virtual void SetUp()
- {
- qt_ = new QT(QT::BoundingBox(0, 0, width_, height_));
- }
- virtual void TearDown()
- {
- delete qt_;
- }
+ virtual void SetUp() { qt_ = new QT(QT::BoundingBox(0, 0, width_, height_)); }
+ virtual void TearDown() { delete qt_; }
- int width_ = 100;
- int height_ = 100;
- QT *qt_;
+ int width_ = 100;
+ int height_ = 100;
+ QT *qt_;
};
// Test that we're doing bound checking correctly.
@@ -66,7 +61,7 @@ TEST_F(QuadTreeTest, insert_count)
int x1 = x0 + w;
int y1 = y0 + h;
ASSERT_TRUE(qt_->insert(QT::BoundingBox(x0, y0, x1, y1), i));
- ASSERT_EQ(qt_->size(), i+1);
+ ASSERT_EQ(qt_->size(), i + 1);
}
// Add 100000 random points.
for (unsigned int i = 0; i < 100000; i++) {
@@ -75,7 +70,7 @@ TEST_F(QuadTreeTest, insert_count)
int x1 = x0;
int y1 = y0;
ASSERT_TRUE(qt_->insert(QT::BoundingBox(x0, y0, x1, y1), i));
- ASSERT_EQ(qt_->size(), i+10001);
+ ASSERT_EQ(qt_->size(), i + 10001);
}
}
@@ -91,8 +86,8 @@ TEST_F(QuadTreeTest, insert_retrieve_same)
int y0 = rng.rng(height_);
int w = rng.rng(width_ - x0);
int h = rng.rng(width_ - y0);
- int x1 = x0 + w/4;
- int y1 = y0 + h/4;
+ int x1 = x0 + w / 4;
+ int y1 = y0 + h / 4;
ASSERT_TRUE(qt_->insert(QT::BoundingBox(x0, y0, x1, y1), i));
}
@@ -103,12 +98,12 @@ TEST_F(QuadTreeTest, insert_retrieve_same)
int y0 = rng.rng(height_);
int w = rng.rng(width_ - x0);
int h = rng.rng(width_ - y0);
- int x1 = x0 + w/4;
- int y1 = y0 + h/4;
+ int x1 = x0 + w / 4;
+ int y1 = y0 + h / 4;
// try to find something in the middle of the square
- int x = (x1-x0)/2+x0;
- int y = (y1-y0)/2+y0;
+ int x = (x1 - x0) / 2 + x0;
+ int y = (y1 - y0) / 2 + y0;
auto res = qt_->get(x, y);
// Somewhat arbirary test to make sure we don't return obscene