Merge branch 'redist_slack' into 'redist_slack'
Redist slack See merge request eddiehung/nextpnr!13
This commit is contained in:
commit
7c1a7e7596
@ -191,8 +191,12 @@ foreach (family ${ARCH})
|
|||||||
# Add any new per-architecture targets here
|
# Add any new per-architecture targets here
|
||||||
if (BUILD_TESTS)
|
if (BUILD_TESTS)
|
||||||
aux_source_directory(tests/${family}/ ${ufamily}_TEST_FILES)
|
aux_source_directory(tests/${family}/ ${ufamily}_TEST_FILES)
|
||||||
|
if (BUILD_GUI)
|
||||||
|
aux_source_directory(tests/gui/ GUI_TEST_FILES)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_executable(nextpnr-${family}-test ${${ufamily}_TEST_FILES} ${COMMON_FILES} ${${ufamily}_FILES})
|
add_executable(nextpnr-${family}-test ${${ufamily}_TEST_FILES}
|
||||||
|
${COMMON_FILES} ${${ufamily}_FILES} ${GUI_TEST_FILES})
|
||||||
target_link_libraries(nextpnr-${family}-test PRIVATE gtest_main)
|
target_link_libraries(nextpnr-${family}-test PRIVATE gtest_main)
|
||||||
add_sanitizers(nextpnr-${family}-test)
|
add_sanitizers(nextpnr-${family}-test)
|
||||||
|
|
||||||
|
@ -204,6 +204,11 @@ struct DecalXY
|
|||||||
{
|
{
|
||||||
DecalId decal;
|
DecalId decal;
|
||||||
float x = 0, y = 0;
|
float x = 0, y = 0;
|
||||||
|
|
||||||
|
bool operator==(const DecalXY &other) const
|
||||||
|
{
|
||||||
|
return (decal == other.decal && x == other.x && y == other.y);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BelPin
|
struct BelPin
|
||||||
|
@ -75,10 +75,10 @@ class SAPlacer
|
|||||||
bool place()
|
bool place()
|
||||||
{
|
{
|
||||||
log_break();
|
log_break();
|
||||||
|
ctx->lock();
|
||||||
|
|
||||||
size_t placed_cells = 0;
|
size_t placed_cells = 0;
|
||||||
// Initial constraints placer
|
// Initial constraints placer
|
||||||
ctx->lock();
|
|
||||||
for (auto &cell_entry : ctx->cells) {
|
for (auto &cell_entry : ctx->cells) {
|
||||||
CellInfo *cell = cell_entry.second.get();
|
CellInfo *cell = cell_entry.second.get();
|
||||||
auto loc = cell->attrs.find(ctx->id("BEL"));
|
auto loc = cell->attrs.find(ctx->id("BEL"));
|
||||||
@ -112,6 +112,7 @@ class SAPlacer
|
|||||||
}
|
}
|
||||||
int constr_placed_cells = placed_cells;
|
int constr_placed_cells = placed_cells;
|
||||||
log_info("Placed %d cells based on constraints.\n", int(placed_cells));
|
log_info("Placed %d cells based on constraints.\n", int(placed_cells));
|
||||||
|
ctx->yield();
|
||||||
|
|
||||||
// Sort to-place cells for deterministic initial placement
|
// Sort to-place cells for deterministic initial placement
|
||||||
std::vector<CellInfo *> autoplaced;
|
std::vector<CellInfo *> autoplaced;
|
||||||
@ -123,28 +124,25 @@ class SAPlacer
|
|||||||
}
|
}
|
||||||
std::sort(autoplaced.begin(), autoplaced.end(), [](CellInfo *a, CellInfo *b) { return a->name < b->name; });
|
std::sort(autoplaced.begin(), autoplaced.end(), [](CellInfo *a, CellInfo *b) { return a->name < b->name; });
|
||||||
ctx->shuffle(autoplaced);
|
ctx->shuffle(autoplaced);
|
||||||
ctx->unlock();
|
|
||||||
|
|
||||||
// Place cells randomly initially
|
// Place cells randomly initially
|
||||||
log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size()));
|
log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size()));
|
||||||
|
|
||||||
for (auto cell : autoplaced) {
|
for (auto cell : autoplaced) {
|
||||||
ctx->lock();
|
|
||||||
place_initial(cell);
|
place_initial(cell);
|
||||||
placed_cells++;
|
placed_cells++;
|
||||||
if ((placed_cells - constr_placed_cells) % 500 == 0)
|
if ((placed_cells - constr_placed_cells) % 500 == 0)
|
||||||
log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells),
|
log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells),
|
||||||
int(autoplaced.size()));
|
int(autoplaced.size()));
|
||||||
ctx->unlock();
|
|
||||||
}
|
}
|
||||||
if ((placed_cells - constr_placed_cells) % 500 != 0)
|
if ((placed_cells - constr_placed_cells) % 500 != 0)
|
||||||
log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells),
|
log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells),
|
||||||
int(autoplaced.size()));
|
int(autoplaced.size()));
|
||||||
|
ctx->yield();
|
||||||
|
|
||||||
log_info("Running simulated annealing placer.\n");
|
log_info("Running simulated annealing placer.\n");
|
||||||
|
|
||||||
// Calculate metric after initial placement
|
// Calculate metric after initial placement
|
||||||
ctx->lock();
|
|
||||||
curr_metric = 0;
|
curr_metric = 0;
|
||||||
curr_tns = 0;
|
curr_tns = 0;
|
||||||
for (auto &net : ctx->nets) {
|
for (auto &net : ctx->nets) {
|
||||||
@ -152,7 +150,6 @@ class SAPlacer
|
|||||||
metrics[net.first] = wl;
|
metrics[net.first] = wl;
|
||||||
curr_metric += wl;
|
curr_metric += wl;
|
||||||
}
|
}
|
||||||
ctx->unlock();
|
|
||||||
|
|
||||||
int n_no_progress = 0;
|
int n_no_progress = 0;
|
||||||
wirelen_t min_metric = curr_metric;
|
wirelen_t min_metric = curr_metric;
|
||||||
@ -257,7 +254,7 @@ class SAPlacer
|
|||||||
ctx->yield();
|
ctx->yield();
|
||||||
}
|
}
|
||||||
// Final post-pacement validitiy check
|
// Final post-pacement validitiy check
|
||||||
ctx->lock();
|
ctx->yield();
|
||||||
for (auto bel : ctx->getBels()) {
|
for (auto bel : ctx->getBels()) {
|
||||||
IdString cell = ctx->getBoundBelCell(bel);
|
IdString cell = ctx->getBoundBelCell(bel);
|
||||||
if (!ctx->isBelLocationValid(bel)) {
|
if (!ctx->isBelLocationValid(bel)) {
|
||||||
@ -275,7 +272,7 @@ class SAPlacer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compute_fmax(ctx, true /* print_fmax */);
|
timing_analysis(ctx, true /* print_fmax */);
|
||||||
ctx->unlock();
|
ctx->unlock();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -814,7 +814,7 @@ bool router1(Context *ctx)
|
|||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
ctx->check();
|
ctx->check();
|
||||||
#endif
|
#endif
|
||||||
compute_fmax(ctx, true /* print_fmax */, true /* print_path */);
|
timing_analysis(ctx, true /* print_fmax */, true /* print_path */);
|
||||||
ctx->unlock();
|
ctx->unlock();
|
||||||
return true;
|
return true;
|
||||||
} catch (log_execution_error_exception) {
|
} catch (log_execution_error_exception) {
|
||||||
|
@ -26,14 +26,13 @@
|
|||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
typedef std::unordered_map<const PortInfo *, delay_t> UpdateMap;
|
|
||||||
typedef std::list<const PortRef *> PortRefList;
|
typedef std::list<const PortRef *> PortRefList;
|
||||||
|
|
||||||
static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack, UpdateMap *updates,
|
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 &min_slack, PortRefList *current_path, PortRefList *crit_path);
|
||||||
|
|
||||||
// Follow a path, returning budget to annotate
|
// Follow a path, returning budget to annotate
|
||||||
static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, delay_t slack, UpdateMap *updates,
|
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 &min_slack, PortRefList *current_path, PortRefList *crit_path)
|
||||||
{
|
{
|
||||||
delay_t value;
|
delay_t value;
|
||||||
@ -58,7 +57,7 @@ static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, de
|
|||||||
if (is_path) {
|
if (is_path) {
|
||||||
NetInfo *net = port.second.net;
|
NetInfo *net = port.second.net;
|
||||||
if (net) {
|
if (net) {
|
||||||
delay_t path_budget = follow_net(ctx, net, path_length, slack - comb_delay, updates, min_slack,
|
delay_t path_budget = follow_net(ctx, net, path_length, slack - comb_delay, update, min_slack,
|
||||||
current_path, crit_path);
|
current_path, crit_path);
|
||||||
value = std::min(value, path_budget);
|
value = std::min(value, path_budget);
|
||||||
}
|
}
|
||||||
@ -66,16 +65,10 @@ static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, de
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updates) {
|
|
||||||
auto ret = updates->emplace(&user.cell->ports.at(user.port), value);
|
|
||||||
if (!ret.second)
|
|
||||||
ret.first->second = std::min(value, ret.first->second);
|
|
||||||
}
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack, UpdateMap *updates,
|
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 &min_slack, PortRefList *current_path, PortRefList *crit_path)
|
||||||
{
|
{
|
||||||
delay_t net_budget = slack / (path_length + 1);
|
delay_t net_budget = slack / (path_length + 1);
|
||||||
@ -92,14 +85,16 @@ static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t s
|
|||||||
}
|
}
|
||||||
net_budget = std::min(net_budget,
|
net_budget = std::min(net_budget,
|
||||||
follow_user_port(ctx, usr, pl, slack - ctx->getNetinfoRouteDelay(net, i),
|
follow_user_port(ctx, usr, pl, slack - ctx->getNetinfoRouteDelay(net, i),
|
||||||
updates, min_slack, current_path, crit_path));
|
update, min_slack, current_path, crit_path));
|
||||||
|
if (update)
|
||||||
|
usr.budget = std::min(usr.budget, net_budget);
|
||||||
if (crit_path)
|
if (crit_path)
|
||||||
current_path->pop_back();
|
current_path->pop_back();
|
||||||
}
|
}
|
||||||
return net_budget;
|
return net_budget;
|
||||||
}
|
}
|
||||||
|
|
||||||
static delay_t compute_min_slack(Context *ctx, UpdateMap *updates, PortRefList *crit_path)
|
static delay_t walk_paths(Context *ctx, bool update, PortRefList *crit_path)
|
||||||
{
|
{
|
||||||
delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
|
delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
|
||||||
delay_t min_slack = default_slack;
|
delay_t min_slack = default_slack;
|
||||||
@ -119,7 +114,7 @@ static delay_t compute_min_slack(Context *ctx, UpdateMap *updates, PortRefList *
|
|||||||
if (ctx->getCellDelay(cell.second.get(), clock_domain, port.first, clkToQ))
|
if (ctx->getCellDelay(cell.second.get(), clock_domain, port.first, clkToQ))
|
||||||
slack -= clkToQ;
|
slack -= clkToQ;
|
||||||
if (port.second.net)
|
if (port.second.net)
|
||||||
follow_net(ctx, port.second.net, 0, slack, updates, min_slack, ¤t_path, crit_path);
|
follow_net(ctx, port.second.net, 0, slack, update, min_slack, ¤t_path, crit_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,32 +135,10 @@ void assign_budget(Context *ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateMap updates;
|
delay_t min_slack = walk_paths(ctx, true, nullptr);
|
||||||
delay_t min_slack = compute_min_slack(ctx, &updates, nullptr);
|
|
||||||
|
|
||||||
// If user has not specified a frequency, adjust the target frequency dynamically
|
|
||||||
// TODO(eddieh): Tune these factors
|
|
||||||
if (!ctx->user_freq) {
|
|
||||||
if (min_slack < 0)
|
|
||||||
ctx->target_freq = 1e12 / (default_slack - 0.95 * min_slack);
|
|
||||||
else
|
|
||||||
ctx->target_freq = 1e12 / (default_slack - 1.2 * 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the budgets
|
|
||||||
for (auto &net : ctx->nets) {
|
for (auto &net : ctx->nets) {
|
||||||
for (size_t i = 0; i < net.second->users.size(); ++i) {
|
for (auto &user : net.second->users) {
|
||||||
auto &user = net.second->users[i];
|
|
||||||
auto pi = &user.cell->ports.at(user.port);
|
|
||||||
auto budget = ctx->getNetinfoRouteDelay(net.second.get(), i);
|
|
||||||
auto it = updates.find(pi);
|
|
||||||
if (it != updates.end())
|
|
||||||
budget += it->second;
|
|
||||||
user.budget = ctx->getBudgetOverride(net.second.get(), i, budget);
|
|
||||||
|
|
||||||
// Post-update check
|
// Post-update check
|
||||||
if (ctx->user_freq && user.budget < 0)
|
if (ctx->user_freq && user.budget < 0)
|
||||||
log_warning("port %s.%s, connected to net '%s', has negative "
|
log_warning("port %s.%s, connected to net '%s', has negative "
|
||||||
@ -180,27 +153,29 @@ void assign_budget(Context *ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If user has not specified a frequency, adjust the target frequency dynamically
|
||||||
|
// TODO(eddieh): Tune these factors
|
||||||
|
if (!ctx->user_freq) {
|
||||||
|
if (min_slack < 0)
|
||||||
|
ctx->target_freq = 1e12 / (default_slack - 0.95 * min_slack);
|
||||||
|
else
|
||||||
|
ctx->target_freq = 1e12 / (default_slack - 1.2 * 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);
|
||||||
|
}
|
||||||
|
|
||||||
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_budget(Context *ctx)
|
void update_budget(Context *ctx)
|
||||||
{
|
{
|
||||||
UpdateMap updates;
|
delay_t min_slack = walk_paths(ctx, true, nullptr);
|
||||||
delay_t min_slack = compute_min_slack(ctx, &updates, nullptr);
|
|
||||||
|
|
||||||
// Update the budgets
|
if (ctx->verbose) {
|
||||||
for (auto &net : ctx->nets) {
|
for (auto &net : ctx->nets) {
|
||||||
for (size_t i = 0; i < net.second->users.size(); ++i) {
|
for (auto &user : net.second->users) {
|
||||||
auto &user = net.second->users[i];
|
// Post-update check
|
||||||
auto pi = &user.cell->ports.at(user.port);
|
|
||||||
auto budget = ctx->getNetinfoRouteDelay(net.second.get(), i);
|
|
||||||
auto it = updates.find(pi);
|
|
||||||
if (it != updates.end())
|
|
||||||
budget += it->second;
|
|
||||||
user.budget = ctx->getBudgetOverride(net.second.get(), i, budget);
|
|
||||||
|
|
||||||
// Post-update check
|
|
||||||
if (ctx->verbose) {
|
|
||||||
if (ctx->user_freq && user.budget < 0)
|
if (ctx->user_freq && user.budget < 0)
|
||||||
log_warning("port %s.%s, connected to net '%s', has negative "
|
log_warning("port %s.%s, connected to net '%s', has negative "
|
||||||
"timing budget of %fns\n",
|
"timing budget of %fns\n",
|
||||||
@ -215,21 +190,25 @@ void update_budget(Context *ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If user has not specified a frequency, adjust the frequency dynamically:
|
// If user has not specified a frequency, adjust the target frequency dynamically
|
||||||
|
// TODO(eddieh): Tune these factors
|
||||||
if (!ctx->user_freq) {
|
if (!ctx->user_freq) {
|
||||||
delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
|
delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
|
||||||
ctx->target_freq = 1e12 / (default_slack - min_slack);
|
if (min_slack < 0)
|
||||||
|
ctx->target_freq = 1e12 / (default_slack - 0.95 * min_slack);
|
||||||
|
else
|
||||||
|
ctx->target_freq = 1e12 / (default_slack - 1.2 * min_slack);
|
||||||
if (ctx->verbose)
|
if (ctx->verbose)
|
||||||
log_info("minimum slack for this update = %d, target Fmax for next update = %.2f MHz\n", min_slack,
|
log_info("minimum slack for this assign = %d, target Fmax for next update = %.2f MHz\n", min_slack,
|
||||||
ctx->target_freq / 1e6);
|
ctx->target_freq / 1e6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void compute_fmax(Context *ctx, bool print_fmax, bool print_path)
|
delay_t timing_analysis(Context *ctx, bool print_fmax, bool print_path)
|
||||||
{
|
{
|
||||||
delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
|
delay_t default_slack = delay_t(1.0e12 / ctx->target_freq);
|
||||||
PortRefList crit_path;
|
PortRefList crit_path;
|
||||||
delay_t min_slack = compute_min_slack(ctx, nullptr, &crit_path);
|
delay_t min_slack = walk_paths(ctx, false, &crit_path);
|
||||||
if (print_path) {
|
if (print_path) {
|
||||||
delay_t total = 0;
|
delay_t total = 0;
|
||||||
log_break();
|
log_break();
|
||||||
@ -267,6 +246,7 @@ void compute_fmax(Context *ctx, bool print_fmax, bool print_path)
|
|||||||
}
|
}
|
||||||
if (print_fmax)
|
if (print_fmax)
|
||||||
log_info("estimated Fmax = %.2f MHz\n", 1e6 / (default_slack - min_slack));
|
log_info("estimated Fmax = %.2f MHz\n", 1e6 / (default_slack - min_slack));
|
||||||
|
return min_slack;
|
||||||
}
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -30,7 +30,9 @@ void assign_budget(Context *ctx);
|
|||||||
// Evenly redistribute the total path slack amongst all sinks on each path
|
// Evenly redistribute the total path slack amongst all sinks on each path
|
||||||
void update_budget(Context *ctx);
|
void update_budget(Context *ctx);
|
||||||
|
|
||||||
void compute_fmax(Context *ctx, bool print_fmax = false, bool print_path = 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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ bool valid_loc_for_io(IOType type, PortType dir, IOSide side, int z)
|
|||||||
return false;
|
return false;
|
||||||
if (is_differential(type) && (!is_lr || ((z % 2) == 1)))
|
if (is_differential(type) && (!is_lr || ((z % 2) == 1)))
|
||||||
return false;
|
return false;
|
||||||
if ((type == IOType::LVCMOS18D || type == IOType::LVDS) && (dir == PORT_OUT || PORT_INOUT) && z != 0)
|
if ((type == IOType::LVCMOS18D || type == IOType::LVDS) && (dir == PORT_OUT || dir == PORT_INOUT) && z != 0)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,8 @@ class BinaryBlobAssembler:
|
|||||||
print("ref %s %s" % (name, comment))
|
print("ref %s %s" % (name, comment))
|
||||||
|
|
||||||
def s(self, s, comment):
|
def s(self, s, comment):
|
||||||
print("str %s" % s)
|
assert "|" not in s
|
||||||
|
print("str |%s| %s" % (s, comment))
|
||||||
|
|
||||||
def u8(self, v, comment):
|
def u8(self, v, comment):
|
||||||
if comment is None:
|
if comment is None:
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
* nextpnr -- Next Generation Place and Route
|
* nextpnr -- Next Generation Place and Route
|
||||||
*
|
*
|
||||||
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
|
* 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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
@ -80,8 +81,11 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr<Context> context, QWidget *parent
|
|||||||
centralTabWidget->tabBar()->tabButton(0, QTabBar::RightSide)->resize(0, 0);
|
centralTabWidget->tabBar()->tabButton(0, QTabBar::RightSide)->resize(0, 0);
|
||||||
|
|
||||||
connect(this, SIGNAL(contextChanged(Context *)), fpgaView, SLOT(newContext(Context *)));
|
connect(this, SIGNAL(contextChanged(Context *)), fpgaView, SLOT(newContext(Context *)));
|
||||||
connect(designview, SIGNAL(selected(std::vector<DecalXY>)), fpgaView,
|
connect(designview, SIGNAL(selected(std::vector<DecalXY>, bool)), fpgaView,
|
||||||
SLOT(onSelectedArchItem(std::vector<DecalXY>)));
|
SLOT(onSelectedArchItem(std::vector<DecalXY>, bool)));
|
||||||
|
connect(fpgaView, SIGNAL(clickedBel(BelId, bool)), designview, SLOT(onClickedBel(BelId, bool)));
|
||||||
|
connect(fpgaView, SIGNAL(clickedWire(WireId, bool)), designview, SLOT(onClickedWire(WireId, bool)));
|
||||||
|
connect(fpgaView, SIGNAL(clickedPip(PipId, bool)), designview, SLOT(onClickedPip(PipId, bool)));
|
||||||
|
|
||||||
connect(designview, SIGNAL(highlight(std::vector<DecalXY>, int)), fpgaView,
|
connect(designview, SIGNAL(highlight(std::vector<DecalXY>, int)), fpgaView,
|
||||||
SLOT(onHighlightGroupChanged(std::vector<DecalXY>, int)));
|
SLOT(onHighlightGroupChanged(std::vector<DecalXY>, int)));
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
* nextpnr -- Next Generation Place and Route
|
* nextpnr -- Next Generation Place and Route
|
||||||
*
|
*
|
||||||
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
|
* 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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
@ -507,6 +508,27 @@ QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name)
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DesignWidget::onClickedBel(BelId bel, bool keep)
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *item = nameToItem[getElementIndex(ElementType::BEL)].value(ctx->getBelName(bel).c_str(ctx));
|
||||||
|
treeWidget->setCurrentItem(item);
|
||||||
|
Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesignWidget::onClickedWire(WireId wire, bool keep)
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *item = nameToItem[getElementIndex(ElementType::WIRE)].value(ctx->getWireName(wire).c_str(ctx));
|
||||||
|
treeWidget->setCurrentItem(item);
|
||||||
|
Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesignWidget::onClickedPip(PipId pip, bool keep)
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *item = nameToItem[getElementIndex(ElementType::PIP)].value(ctx->getPipName(pip).c_str(ctx));
|
||||||
|
treeWidget->setCurrentItem(item);
|
||||||
|
Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep);
|
||||||
|
}
|
||||||
|
|
||||||
void DesignWidget::onItemSelectionChanged()
|
void DesignWidget::onItemSelectionChanged()
|
||||||
{
|
{
|
||||||
if (treeWidget->selectedItems().size() == 0)
|
if (treeWidget->selectedItems().size() == 0)
|
||||||
@ -520,7 +542,7 @@ void DesignWidget::onItemSelectionChanged()
|
|||||||
std::vector<DecalXY> d = getDecals(type, value);
|
std::vector<DecalXY> d = getDecals(type, value);
|
||||||
std::move(d.begin(), d.end(), std::back_inserter(decals));
|
std::move(d.begin(), d.end(), std::back_inserter(decals));
|
||||||
}
|
}
|
||||||
Q_EMIT selected(decals);
|
Q_EMIT selected(decals, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,7 +563,7 @@ void DesignWidget::onItemSelectionChanged()
|
|||||||
clearProperties();
|
clearProperties();
|
||||||
|
|
||||||
IdString c = static_cast<IdStringTreeItem *>(clickItem)->getData();
|
IdString c = static_cast<IdStringTreeItem *>(clickItem)->getData();
|
||||||
Q_EMIT selected(getDecals(type, c));
|
Q_EMIT selected(getDecals(type, c), false);
|
||||||
|
|
||||||
if (type == ElementType::BEL) {
|
if (type == ElementType::BEL) {
|
||||||
BelId bel = ctx->getBelByName(c);
|
BelId bel = ctx->getBelByName(c);
|
||||||
@ -833,7 +855,7 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos)
|
|||||||
std::vector<DecalXY> d = getDecals(type, value);
|
std::vector<DecalXY> d = getDecals(type, value);
|
||||||
std::move(d.begin(), d.end(), std::back_inserter(decals));
|
std::move(d.begin(), d.end(), std::back_inserter(decals));
|
||||||
}
|
}
|
||||||
Q_EMIT selected(decals);
|
Q_EMIT selected(decals, false);
|
||||||
});
|
});
|
||||||
menu.addAction(selectAction);
|
menu.addAction(selectAction);
|
||||||
|
|
||||||
|
@ -37,7 +37,8 @@ enum class ElementType
|
|||||||
WIRE,
|
WIRE,
|
||||||
PIP,
|
PIP,
|
||||||
NET,
|
NET,
|
||||||
CELL
|
CELL,
|
||||||
|
GROUP
|
||||||
};
|
};
|
||||||
|
|
||||||
class DesignWidget : public QWidget
|
class DesignWidget : public QWidget
|
||||||
@ -63,7 +64,7 @@ class DesignWidget : public QWidget
|
|||||||
void updateHighlightGroup(QList<QTreeWidgetItem *> item, int group);
|
void updateHighlightGroup(QList<QTreeWidgetItem *> item, int group);
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void info(std::string text);
|
void info(std::string text);
|
||||||
void selected(std::vector<DecalXY> decal);
|
void selected(std::vector<DecalXY> decal, bool keep);
|
||||||
void highlight(std::vector<DecalXY> decal, int group);
|
void highlight(std::vector<DecalXY> decal, int group);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
@ -74,6 +75,9 @@ class DesignWidget : public QWidget
|
|||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void newContext(Context *ctx);
|
void newContext(Context *ctx);
|
||||||
void updateTree();
|
void updateTree();
|
||||||
|
void onClickedBel(BelId bel, bool keep);
|
||||||
|
void onClickedWire(WireId wire, bool keep);
|
||||||
|
void onClickedPip(PipId pip, bool keep);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Context *ctx;
|
Context *ctx;
|
||||||
|
@ -31,11 +31,9 @@
|
|||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
FPGAViewWidget::FPGAViewWidget(QWidget *parent) :
|
FPGAViewWidget::FPGAViewWidget(QWidget *parent)
|
||||||
QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this),
|
: QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this), lineShader_(this), zoom_(10.0f),
|
||||||
lineShader_(this), zoom_(500.0f),
|
rendererArgs_(new FPGAViewWidget::RendererArgs), rendererData_(new FPGAViewWidget::RendererData)
|
||||||
rendererData_(new FPGAViewWidget::RendererData),
|
|
||||||
rendererArgs_(new FPGAViewWidget::RendererArgs)
|
|
||||||
{
|
{
|
||||||
colors_.background = QColor("#000000");
|
colors_.background = QColor("#000000");
|
||||||
colors_.grid = QColor("#333");
|
colors_.grid = QColor("#333");
|
||||||
@ -44,6 +42,7 @@ FPGAViewWidget::FPGAViewWidget(QWidget *parent) :
|
|||||||
colors_.inactive = QColor("#303030");
|
colors_.inactive = QColor("#303030");
|
||||||
colors_.active = QColor("#f0f0f0");
|
colors_.active = QColor("#f0f0f0");
|
||||||
colors_.selected = QColor("#ff6600");
|
colors_.selected = QColor("#ff6600");
|
||||||
|
colors_.hovered = QColor("#906030");
|
||||||
colors_.highlight[0] = QColor("#6495ed");
|
colors_.highlight[0] = QColor("#6495ed");
|
||||||
colors_.highlight[1] = QColor("#7fffd4");
|
colors_.highlight[1] = QColor("#7fffd4");
|
||||||
colors_.highlight[2] = QColor("#98fb98");
|
colors_.highlight[2] = QColor("#98fb98");
|
||||||
@ -53,7 +52,8 @@ FPGAViewWidget::FPGAViewWidget(QWidget *parent) :
|
|||||||
colors_.highlight[6] = QColor("#ff69b4");
|
colors_.highlight[6] = QColor("#ff69b4");
|
||||||
colors_.highlight[7] = QColor("#da70d6");
|
colors_.highlight[7] = QColor("#da70d6");
|
||||||
|
|
||||||
rendererArgs_->highlightedOrSelectedChanged = false;
|
rendererArgs_->changed = false;
|
||||||
|
rendererArgs_->flags.zoomOutbound = true;
|
||||||
|
|
||||||
auto fmt = format();
|
auto fmt = format();
|
||||||
fmt.setMajorVersion(3);
|
fmt.setMajorVersion(3);
|
||||||
@ -75,6 +75,7 @@ FPGAViewWidget::FPGAViewWidget(QWidget *parent) :
|
|||||||
renderRunner_ = std::unique_ptr<PeriodicRunner>(new PeriodicRunner(this, [this] { renderLines(); }));
|
renderRunner_ = std::unique_ptr<PeriodicRunner>(new PeriodicRunner(this, [this] { renderLines(); }));
|
||||||
renderRunner_->start();
|
renderRunner_->start();
|
||||||
renderRunner_->startTimer(1000 / 2); // render lines 2 times per second
|
renderRunner_->startTimer(1000 / 2); // render lines 2 times per second
|
||||||
|
setMouseTracking(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
FPGAViewWidget::~FPGAViewWidget() {}
|
FPGAViewWidget::~FPGAViewWidget() {}
|
||||||
@ -82,9 +83,13 @@ FPGAViewWidget::~FPGAViewWidget() {}
|
|||||||
void FPGAViewWidget::newContext(Context *ctx)
|
void FPGAViewWidget::newContext(Context *ctx)
|
||||||
{
|
{
|
||||||
ctx_ = ctx;
|
ctx_ = ctx;
|
||||||
onSelectedArchItem(std::vector<DecalXY>());
|
onSelectedArchItem(std::vector<DecalXY>(), false);
|
||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
onHighlightGroupChanged(std::vector<DecalXY>(), i);
|
onHighlightGroupChanged(std::vector<DecalXY>(), i);
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&rendererArgsLock_);
|
||||||
|
rendererArgs_->flags.zoomOutbound = true;
|
||||||
|
}
|
||||||
pokeRenderer();
|
pokeRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,36 +107,112 @@ void FPGAViewWidget::initializeGL()
|
|||||||
0.0);
|
0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::drawGraphicElement(LineShaderData &out, const GraphicElement &el, float x, float y)
|
float FPGAViewWidget::PickedElement::distance(Context *ctx, float wx, float wy) const
|
||||||
{
|
{
|
||||||
const float scale = 1.0;
|
// Get DecalXY for this element.
|
||||||
|
DecalXY dec = decal(ctx);
|
||||||
|
|
||||||
|
// Coordinates within decal.
|
||||||
|
float dx = wx - dec.x;
|
||||||
|
float dy = wy - dec.y;
|
||||||
|
|
||||||
|
auto graphics = ctx->getDecalGraphics(dec.decal);
|
||||||
|
if (graphics.size() == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// TODO(q3k): For multi-line decals, find intersections and also calculate distance to them.
|
||||||
|
|
||||||
|
// Go over its' GraphicElements, and calculate the distance to them.
|
||||||
|
std::vector<float> distances;
|
||||||
|
std::transform(graphics.begin(), graphics.end(), std::back_inserter(distances),
|
||||||
|
[&](const GraphicElement &ge) -> float {
|
||||||
|
switch (ge.type) {
|
||||||
|
case GraphicElement::TYPE_BOX: {
|
||||||
|
// If outside the box, return unit distance to closest border.
|
||||||
|
float outside_x = -1, outside_y = -1;
|
||||||
|
if (dx < ge.x1 || dx > ge.x2) {
|
||||||
|
outside_x = std::min(std::abs(dx - ge.x1), std::abs(dx - ge.x2));
|
||||||
|
}
|
||||||
|
if (dy < ge.y1 || dy > ge.y2) {
|
||||||
|
outside_y = std::min(std::abs(dy - ge.y1), std::abs(dy - ge.y2));
|
||||||
|
}
|
||||||
|
if (outside_x != -1 && outside_y != -1)
|
||||||
|
return std::min(outside_x, outside_y);
|
||||||
|
|
||||||
|
// If in box, return 0.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case GraphicElement::TYPE_LINE:
|
||||||
|
case GraphicElement::TYPE_ARROW: {
|
||||||
|
// Return somewhat primitively calculated distance to segment.
|
||||||
|
// TODO(q3k): consider coming up with a better algorithm
|
||||||
|
QVector2D w(wx, wy);
|
||||||
|
QVector2D a(ge.x1, ge.y1);
|
||||||
|
QVector2D b(ge.x2, ge.y2);
|
||||||
|
float dw = a.distanceToPoint(w) + b.distanceToPoint(w);
|
||||||
|
float dab = a.distanceToPoint(b);
|
||||||
|
return std::abs(dw - dab) / dab;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Not close to antyhing.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find smallest non -1 distance.
|
||||||
|
// Find closest element.
|
||||||
|
return *std::min_element(distances.begin(), distances.end(), [&](float a, float b) {
|
||||||
|
if (a == -1)
|
||||||
|
return false;
|
||||||
|
if (b == -1)
|
||||||
|
return true;
|
||||||
|
return a < b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPGAViewWidget::renderGraphicElement(LineShaderData &out, PickQuadTree::BoundingBox &bb, const GraphicElement &el,
|
||||||
|
float x, float y)
|
||||||
|
{
|
||||||
if (el.type == GraphicElement::TYPE_BOX) {
|
if (el.type == GraphicElement::TYPE_BOX) {
|
||||||
auto line = PolyLine(true);
|
auto line = PolyLine(true);
|
||||||
line.point(x + scale * el.x1, y + scale * el.y1);
|
line.point(x + el.x1, y + el.y1);
|
||||||
line.point(x + scale * el.x2, y + scale * el.y1);
|
line.point(x + el.x2, y + el.y1);
|
||||||
line.point(x + scale * el.x2, y + scale * el.y2);
|
line.point(x + el.x2, y + el.y2);
|
||||||
line.point(x + scale * el.x1, y + scale * el.y2);
|
line.point(x + el.x1, y + el.y2);
|
||||||
line.build(out);
|
line.build(out);
|
||||||
|
|
||||||
|
bb.setX0(std::min(bb.x0(), x + el.x1));
|
||||||
|
bb.setY0(std::min(bb.y0(), y + el.y1));
|
||||||
|
bb.setX1(std::max(bb.x1(), x + el.x2));
|
||||||
|
bb.setY1(std::max(bb.y1(), y + el.y2));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) {
|
if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) {
|
||||||
PolyLine(x + scale * el.x1, y + scale * el.y1, x + scale * el.x2, y + scale * el.y2)
|
PolyLine(x + el.x1, y + el.y1, x + el.x2, y + el.y2).build(out);
|
||||||
.build(out);
|
bb.setX0(std::min(bb.x0(), x + el.x1));
|
||||||
|
bb.setY0(std::min(bb.y0(), y + el.y1));
|
||||||
|
bb.setX1(std::max(bb.x1(), x + el.x2));
|
||||||
|
bb.setY1(std::max(bb.y1(), y + el.y2));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal)
|
void FPGAViewWidget::renderDecal(LineShaderData &out, PickQuadTree::BoundingBox &bb, const DecalXY &decal)
|
||||||
{
|
{
|
||||||
|
if (decal.decal == DecalId())
|
||||||
|
return;
|
||||||
|
|
||||||
float offsetX = decal.x;
|
float offsetX = decal.x;
|
||||||
float offsetY = decal.y;
|
float offsetY = decal.y;
|
||||||
|
|
||||||
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
|
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
|
||||||
drawGraphicElement(out, el, offsetX, offsetY);
|
renderGraphicElement(out, bb, el, offsetX, offsetY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal)
|
void FPGAViewWidget::renderArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], PickQuadTree::BoundingBox &bb,
|
||||||
|
const DecalXY &decal)
|
||||||
{
|
{
|
||||||
float offsetX = decal.x;
|
float offsetX = decal.x;
|
||||||
float offsetY = decal.y;
|
float offsetY = decal.y;
|
||||||
@ -141,7 +222,7 @@ void FPGAViewWidget::drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX]
|
|||||||
case GraphicElement::STYLE_FRAME:
|
case GraphicElement::STYLE_FRAME:
|
||||||
case GraphicElement::STYLE_INACTIVE:
|
case GraphicElement::STYLE_INACTIVE:
|
||||||
case GraphicElement::STYLE_ACTIVE:
|
case GraphicElement::STYLE_ACTIVE:
|
||||||
drawGraphicElement(out[el.style], el, offsetX, offsetY);
|
renderGraphicElement(out[el.style], bb, el, offsetX, offsetY);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -149,13 +230,48 @@ void FPGAViewWidget::drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FPGAViewWidget::populateQuadTree(RendererData *data, const DecalXY &decal, const PickedElement &element)
|
||||||
|
{
|
||||||
|
float x = decal.x;
|
||||||
|
float y = decal.y;
|
||||||
|
|
||||||
|
for (auto &el : ctx_->getDecalGraphics(decal.decal)) {
|
||||||
|
if (el.style == GraphicElement::STYLE_HIDDEN || el.style == GraphicElement::STYLE_FRAME) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.type == GraphicElement::TYPE_BOX) {
|
||||||
|
// Boxes are bounded by themselves.
|
||||||
|
data->qt->insert(PickQuadTree::BoundingBox(x + el.x1, y + el.y1, x + el.x2, y + el.y2), element);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) {
|
||||||
|
// Lines are bounded by their AABB slightly enlarged.
|
||||||
|
float x0 = x + el.x1;
|
||||||
|
float y0 = y + el.y1;
|
||||||
|
float x1 = x + el.x2;
|
||||||
|
float y1 = y + el.y2;
|
||||||
|
if (x1 < x0)
|
||||||
|
std::swap(x0, x1);
|
||||||
|
if (y1 < y0)
|
||||||
|
std::swap(y0, y1);
|
||||||
|
|
||||||
|
x0 -= 0.01;
|
||||||
|
y0 -= 0.01;
|
||||||
|
x1 += 0.01;
|
||||||
|
y1 += 0.01;
|
||||||
|
|
||||||
|
data->qt->insert(PickQuadTree::BoundingBox(x0, y0, x1, y1), element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QMatrix4x4 FPGAViewWidget::getProjection(void)
|
QMatrix4x4 FPGAViewWidget::getProjection(void)
|
||||||
{
|
{
|
||||||
QMatrix4x4 matrix;
|
QMatrix4x4 matrix;
|
||||||
|
|
||||||
const float aspect = float(width()) / float(height());
|
const float aspect = float(width()) / float(height());
|
||||||
matrix.perspective(3.14 / 2, aspect, zoomNear_, zoomFar_);
|
matrix.perspective(90, aspect, zoomNear_, zoomFar_);
|
||||||
matrix.translate(0.0f, 0.0f, -zoom_);
|
|
||||||
return matrix;
|
return matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,12 +283,14 @@ void FPGAViewWidget::paintGL()
|
|||||||
gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
QMatrix4x4 matrix = getProjection();
|
QMatrix4x4 matrix = getProjection();
|
||||||
|
matrix.translate(0.0f, 0.0f, -zoom_);
|
||||||
|
|
||||||
matrix *= viewMove_;
|
matrix *= viewMove_;
|
||||||
|
|
||||||
// Calculate world thickness to achieve a screen 1px/1.1px line.
|
// Calculate world thickness to achieve a screen 1px/1.1px line.
|
||||||
float thick1Px = mouseToWorldCoordinates(1, 0).x();
|
float thick1Px = mouseToWorldDimensions(1, 0).x();
|
||||||
float thick11Px = mouseToWorldCoordinates(1.1, 0).x();
|
float thick11Px = mouseToWorldDimensions(1.1, 0).x();
|
||||||
|
float thick2Px = mouseToWorldDimensions(2, 0).x();
|
||||||
|
|
||||||
// Render grid.
|
// Render grid.
|
||||||
auto grid = LineShaderData();
|
auto grid = LineShaderData();
|
||||||
@ -180,23 +298,40 @@ void FPGAViewWidget::paintGL()
|
|||||||
PolyLine(-100.0f, i, 100.0f, i).build(grid);
|
PolyLine(-100.0f, i, 100.0f, i).build(grid);
|
||||||
PolyLine(i, -100.0f, i, 100.0f).build(grid);
|
PolyLine(i, -100.0f, i, 100.0f).build(grid);
|
||||||
}
|
}
|
||||||
|
// Flags from pipeline.
|
||||||
|
PassthroughFlags flags;
|
||||||
// Draw grid.
|
// Draw grid.
|
||||||
lineShader_.draw(grid, colors_.grid, thick1Px, matrix);
|
lineShader_.draw(grid, colors_.grid, thick1Px, matrix);
|
||||||
|
|
||||||
rendererDataLock_.lock();
|
{
|
||||||
|
QMutexLocker locker(&rendererDataLock_);
|
||||||
|
|
||||||
// Render Arch graphics.
|
// Render Arch graphics.
|
||||||
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_FRAME], colors_.frame, thick11Px, matrix);
|
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_FRAME], colors_.frame, thick11Px, matrix);
|
||||||
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_HIDDEN], colors_.hidden, thick11Px, matrix);
|
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_HIDDEN], colors_.hidden, thick11Px, matrix);
|
||||||
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_INACTIVE], colors_.inactive, thick11Px, matrix);
|
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_INACTIVE], colors_.inactive, thick11Px,
|
||||||
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_ACTIVE], colors_.active, thick11Px, matrix);
|
matrix);
|
||||||
|
lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_ACTIVE], colors_.active, thick11Px, matrix);
|
||||||
|
|
||||||
// Draw highlighted items.
|
// Draw highlighted items.
|
||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
lineShader_.draw(rendererData_->gfxHighlighted[i], colors_.highlight[i], thick11Px, matrix);
|
lineShader_.draw(rendererData_->gfxHighlighted[i], colors_.highlight[i], thick11Px, matrix);
|
||||||
|
|
||||||
lineShader_.draw(rendererData_->gfxSelected, colors_.selected, thick11Px, matrix);
|
lineShader_.draw(rendererData_->gfxSelected, colors_.selected, thick11Px, matrix);
|
||||||
rendererDataLock_.unlock();
|
lineShader_.draw(rendererData_->gfxHovered, colors_.hovered, thick2Px, matrix);
|
||||||
|
|
||||||
|
flags = rendererData_->flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&rendererArgsLock_);
|
||||||
|
rendererArgs_->flags.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check flags passed through pipeline.
|
||||||
|
if (flags.zoomOutbound) {
|
||||||
|
zoomOutbound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::pokeRenderer(void) { renderRunner_->poke(); }
|
void FPGAViewWidget::pokeRenderer(void) { renderRunner_->poke(); }
|
||||||
@ -207,10 +342,10 @@ void FPGAViewWidget::renderLines(void)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Data from Context needed to render all decals.
|
// Data from Context needed to render all decals.
|
||||||
std::vector<DecalXY> belDecals;
|
std::vector<std::pair<DecalXY, BelId>> belDecals;
|
||||||
std::vector<DecalXY> wireDecals;
|
std::vector<std::pair<DecalXY, WireId>> wireDecals;
|
||||||
std::vector<DecalXY> pipDecals;
|
std::vector<std::pair<DecalXY, PipId>> pipDecals;
|
||||||
std::vector<DecalXY> groupDecals;
|
std::vector<std::pair<DecalXY, GroupId>> groupDecals;
|
||||||
bool decalsChanged = false;
|
bool decalsChanged = false;
|
||||||
{
|
{
|
||||||
// Take the UI/Normal mutex on the Context, copy over all we need as
|
// Take the UI/Normal mutex on the Context, copy over all we need as
|
||||||
@ -248,52 +383,86 @@ void FPGAViewWidget::renderLines(void)
|
|||||||
// Local copy of decals, taken as fast as possible to not block the P&R.
|
// Local copy of decals, taken as fast as possible to not block the P&R.
|
||||||
if (decalsChanged) {
|
if (decalsChanged) {
|
||||||
for (auto bel : ctx_->getBels()) {
|
for (auto bel : ctx_->getBels()) {
|
||||||
belDecals.push_back(ctx_->getBelDecal(bel));
|
belDecals.push_back({ctx_->getBelDecal(bel), bel});
|
||||||
}
|
}
|
||||||
for (auto wire : ctx_->getWires()) {
|
for (auto wire : ctx_->getWires()) {
|
||||||
wireDecals.push_back(ctx_->getWireDecal(wire));
|
wireDecals.push_back({ctx_->getWireDecal(wire), wire});
|
||||||
}
|
}
|
||||||
for (auto pip : ctx_->getPips()) {
|
for (auto pip : ctx_->getPips()) {
|
||||||
pipDecals.push_back(ctx_->getPipDecal(pip));
|
pipDecals.push_back({ctx_->getPipDecal(pip), pip});
|
||||||
}
|
}
|
||||||
for (auto group : ctx_->getGroups()) {
|
for (auto group : ctx_->getGroups()) {
|
||||||
groupDecals.push_back(ctx_->getGroupDecal(group));
|
groupDecals.push_back({ctx_->getGroupDecal(group), group});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arguments from the main UI thread on what we should render.
|
// Arguments from the main UI thread on what we should render.
|
||||||
std::vector<DecalXY> selectedDecals;
|
std::vector<DecalXY> selectedDecals;
|
||||||
|
DecalXY hoveredDecal;
|
||||||
std::vector<DecalXY> highlightedDecals[8];
|
std::vector<DecalXY> highlightedDecals[8];
|
||||||
bool highlightedOrSelectedChanged;
|
bool highlightedOrSelectedChanged;
|
||||||
|
PassthroughFlags flags;
|
||||||
{
|
{
|
||||||
// Take the renderer arguments lock, copy over all we need.
|
// Take the renderer arguments lock, copy over all we need.
|
||||||
QMutexLocker lock(&rendererArgsLock_);
|
QMutexLocker lock(&rendererArgsLock_);
|
||||||
|
|
||||||
selectedDecals = rendererArgs_->selectedDecals;
|
selectedDecals = rendererArgs_->selectedDecals;
|
||||||
|
hoveredDecal = rendererArgs_->hoveredDecal;
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
highlightedDecals[i] = rendererArgs_->highlightedDecals[i];
|
highlightedDecals[i] = rendererArgs_->highlightedDecals[i];
|
||||||
highlightedOrSelectedChanged = rendererArgs_->highlightedOrSelectedChanged;
|
|
||||||
rendererArgs_->highlightedOrSelectedChanged = false;
|
highlightedOrSelectedChanged = rendererArgs_->changed;
|
||||||
|
rendererArgs_->changed = false;
|
||||||
|
|
||||||
|
flags = rendererArgs_->flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render decals if necessary.
|
// Render decals if necessary.
|
||||||
if (decalsChanged) {
|
if (decalsChanged) {
|
||||||
auto data = std::unique_ptr<FPGAViewWidget::RendererData>(new FPGAViewWidget::RendererData);
|
auto data = std::unique_ptr<FPGAViewWidget::RendererData>(new FPGAViewWidget::RendererData);
|
||||||
|
// Reset bounding box.
|
||||||
|
data->bbGlobal.clear();
|
||||||
|
|
||||||
// Draw Bels.
|
// Draw Bels.
|
||||||
for (auto const &decal : belDecals) {
|
for (auto const &decal : belDecals) {
|
||||||
drawArchDecal(data->gfxByStyle, decal);
|
renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first);
|
||||||
}
|
}
|
||||||
// Draw Wires.
|
// Draw Wires.
|
||||||
for (auto const &decal : wireDecals) {
|
for (auto const &decal : wireDecals) {
|
||||||
drawArchDecal(data->gfxByStyle, decal);
|
renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first);
|
||||||
}
|
}
|
||||||
// Draw Pips.
|
// Draw Pips.
|
||||||
for (auto const &decal : pipDecals) {
|
for (auto const &decal : pipDecals) {
|
||||||
drawArchDecal(data->gfxByStyle, decal);
|
renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first);
|
||||||
}
|
}
|
||||||
// Draw Groups.
|
// Draw Groups.
|
||||||
for (auto const &decal : groupDecals) {
|
for (auto const &decal : groupDecals) {
|
||||||
drawArchDecal(data->gfxByStyle, decal);
|
renderArchDecal(data->gfxByStyle, data->bbGlobal, decal.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounding box should be calculated by now.
|
||||||
|
NPNR_ASSERT(data->bbGlobal.w() != 0);
|
||||||
|
NPNR_ASSERT(data->bbGlobal.h() != 0);
|
||||||
|
|
||||||
|
// Populate picking quadtree.
|
||||||
|
data->qt = std::unique_ptr<PickQuadTree>(new PickQuadTree(data->bbGlobal));
|
||||||
|
for (auto const &decal : belDecals) {
|
||||||
|
populateQuadTree(data.get(), decal.first,
|
||||||
|
PickedElement::fromBel(decal.second, decal.first.x, decal.first.y));
|
||||||
|
}
|
||||||
|
for (auto const &decal : wireDecals) {
|
||||||
|
populateQuadTree(data.get(), decal.first,
|
||||||
|
PickedElement::fromWire(decal.second, decal.first.x, decal.first.y));
|
||||||
|
}
|
||||||
|
for (auto const &decal : pipDecals) {
|
||||||
|
populateQuadTree(data.get(), decal.first,
|
||||||
|
PickedElement::fromPip(decal.second, decal.first.x, decal.first.y));
|
||||||
|
}
|
||||||
|
for (auto const &decal : groupDecals) {
|
||||||
|
populateQuadTree(data.get(), decal.first,
|
||||||
|
PickedElement::fromGroup(decal.second, decal.first.x, decal.first.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swap over.
|
// Swap over.
|
||||||
@ -304,6 +473,7 @@ void FPGAViewWidget::renderLines(void)
|
|||||||
// copy them over from teh current object.
|
// copy them over from teh current object.
|
||||||
if (!highlightedOrSelectedChanged) {
|
if (!highlightedOrSelectedChanged) {
|
||||||
data->gfxSelected = rendererData_->gfxSelected;
|
data->gfxSelected = rendererData_->gfxSelected;
|
||||||
|
data->gfxHovered = rendererData_->gfxHovered;
|
||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
data->gfxHighlighted[i] = rendererData_->gfxHighlighted[i];
|
data->gfxHighlighted[i] = rendererData_->gfxHighlighted[i];
|
||||||
}
|
}
|
||||||
@ -315,28 +485,48 @@ void FPGAViewWidget::renderLines(void)
|
|||||||
if (highlightedOrSelectedChanged) {
|
if (highlightedOrSelectedChanged) {
|
||||||
QMutexLocker locker(&rendererDataLock_);
|
QMutexLocker locker(&rendererDataLock_);
|
||||||
|
|
||||||
|
// Whether the currently being hovered decal is also selected.
|
||||||
|
bool hoveringSelected = false;
|
||||||
// Render selected.
|
// Render selected.
|
||||||
|
rendererData_->bbSelected.clear();
|
||||||
rendererData_->gfxSelected.clear();
|
rendererData_->gfxSelected.clear();
|
||||||
for (auto &decal : selectedDecals) {
|
for (auto &decal : selectedDecals) {
|
||||||
drawDecal(rendererData_->gfxSelected, decal);
|
if (decal == hoveredDecal)
|
||||||
|
hoveringSelected = true;
|
||||||
|
renderDecal(rendererData_->gfxSelected, rendererData_->bbSelected, decal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render hovered.
|
||||||
|
rendererData_->gfxHovered.clear();
|
||||||
|
if (!hoveringSelected) {
|
||||||
|
renderDecal(rendererData_->gfxHovered, rendererData_->bbGlobal, hoveredDecal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render highlighted.
|
// Render highlighted.
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
rendererData_->gfxHighlighted[i].clear();
|
rendererData_->gfxHighlighted[i].clear();
|
||||||
for (auto &decal : highlightedDecals[i]) {
|
for (auto &decal : highlightedDecals[i]) {
|
||||||
drawDecal(rendererData_->gfxHighlighted[i], decal);
|
renderDecal(rendererData_->gfxHighlighted[i], rendererData_->bbGlobal, decal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&rendererDataLock_);
|
||||||
|
rendererData_->flags = flags;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals)
|
void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals, bool keep)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&rendererArgsLock_);
|
QMutexLocker locker(&rendererArgsLock_);
|
||||||
rendererArgs_->selectedDecals = decals;
|
if (keep) {
|
||||||
rendererArgs_->highlightedOrSelectedChanged = true;
|
std::copy(decals.begin(), decals.end(), std::back_inserter(rendererArgs_->selectedDecals));
|
||||||
|
} else {
|
||||||
|
rendererArgs_->selectedDecals = decals;
|
||||||
|
}
|
||||||
|
rendererArgs_->changed = true;
|
||||||
}
|
}
|
||||||
pokeRenderer();
|
pokeRenderer();
|
||||||
}
|
}
|
||||||
@ -346,20 +536,156 @@ void FPGAViewWidget::onHighlightGroupChanged(std::vector<DecalXY> decals, int gr
|
|||||||
{
|
{
|
||||||
QMutexLocker locker(&rendererArgsLock_);
|
QMutexLocker locker(&rendererArgsLock_);
|
||||||
rendererArgs_->highlightedDecals[group] = decals;
|
rendererArgs_->highlightedDecals[group] = decals;
|
||||||
rendererArgs_->highlightedOrSelectedChanged = true;
|
rendererArgs_->changed = true;
|
||||||
}
|
}
|
||||||
pokeRenderer();
|
pokeRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::resizeGL(int width, int height) {}
|
void FPGAViewWidget::resizeGL(int width, int height) {}
|
||||||
|
|
||||||
void FPGAViewWidget::mousePressEvent(QMouseEvent *event) { lastDragPos_ = event->pos(); }
|
boost::optional<FPGAViewWidget::PickedElement> FPGAViewWidget::pickElement(float worldx, float worldy)
|
||||||
|
{
|
||||||
|
// Get elements from renderer whose BBs correspond to the pick.
|
||||||
|
std::vector<PickedElement> elems;
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&rendererDataLock_);
|
||||||
|
if (rendererData_->qt == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
elems = rendererData_->qt->get(worldx, worldy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elems.size() == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate distances to all elements picked.
|
||||||
|
using ElemDist = std::pair<const PickedElement *, float>;
|
||||||
|
std::vector<ElemDist> distances;
|
||||||
|
std::transform(elems.begin(), elems.end(), std::back_inserter(distances), [&](const PickedElement &e) -> ElemDist {
|
||||||
|
return std::make_pair(&e, e.distance(ctx_, worldx, worldy));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find closest non -1 element.
|
||||||
|
auto closest = std::min_element(distances.begin(), distances.end(), [&](const ElemDist &a, const ElemDist &b) {
|
||||||
|
if (a.second == -1)
|
||||||
|
return false;
|
||||||
|
if (b.second == -1)
|
||||||
|
return true;
|
||||||
|
return a.second < b.second;
|
||||||
|
});
|
||||||
|
|
||||||
|
// All out of reach?
|
||||||
|
if (closest->second < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return *(closest->first);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPGAViewWidget::mousePressEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) {
|
||||||
|
lastDragPos_ = event->pos();
|
||||||
|
}
|
||||||
|
if (event->buttons() & Qt::LeftButton) {
|
||||||
|
bool ctrl = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
|
||||||
|
|
||||||
|
auto world = mouseToWorldCoordinates(event->x(), event->y());
|
||||||
|
auto closestOr = pickElement(world.x(), world.y());
|
||||||
|
if (!closestOr) {
|
||||||
|
// If we clicked on empty space and aren't holding down ctrl,
|
||||||
|
// clear the selection.
|
||||||
|
if (!ctrl) {
|
||||||
|
QMutexLocker locked(&rendererArgsLock_);
|
||||||
|
rendererArgs_->selectedDecals.clear();
|
||||||
|
rendererArgs_->changed = true;
|
||||||
|
pokeRenderer();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto closest = closestOr.value();
|
||||||
|
if (closest.type == ElementType::BEL) {
|
||||||
|
clickedBel(closest.bel, ctrl);
|
||||||
|
} else if (closest.type == ElementType::WIRE) {
|
||||||
|
clickedWire(closest.wire, ctrl);
|
||||||
|
} else if (closest.type == ElementType::PIP) {
|
||||||
|
clickedPip(closest.pip, ctrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) {
|
||||||
|
const int dx = event->x() - lastDragPos_.x();
|
||||||
|
const int dy = event->y() - lastDragPos_.y();
|
||||||
|
lastDragPos_ = event->pos();
|
||||||
|
|
||||||
|
auto world = mouseToWorldDimensions(dx, dy);
|
||||||
|
viewMove_.translate(world.x(), -world.y());
|
||||||
|
|
||||||
|
update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto world = mouseToWorldCoordinates(event->x(), event->y());
|
||||||
|
auto closestOr = pickElement(world.x(), world.y());
|
||||||
|
// No elements? No decal.
|
||||||
|
if (!closestOr) {
|
||||||
|
QMutexLocker locked(&rendererArgsLock_);
|
||||||
|
rendererArgs_->hoveredDecal = DecalXY();
|
||||||
|
rendererArgs_->changed = true;
|
||||||
|
pokeRenderer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto closest = closestOr.value();
|
||||||
|
|
||||||
|
{
|
||||||
|
QMutexLocker locked(&rendererArgsLock_);
|
||||||
|
rendererArgs_->hoveredDecal = closest.decal(ctx_);
|
||||||
|
rendererArgs_->changed = true;
|
||||||
|
pokeRenderer();
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
// Invert the projection matrix to calculate screen/mouse to world/grid
|
// Invert the projection matrix to calculate screen/mouse to world/grid
|
||||||
// coordinates.
|
// coordinates.
|
||||||
QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y)
|
QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y)
|
||||||
|
{
|
||||||
|
const qreal retinaScale = devicePixelRatio();
|
||||||
|
|
||||||
|
auto projection = getProjection();
|
||||||
|
|
||||||
|
QMatrix4x4 vp;
|
||||||
|
vp.viewport(0, 0, width() * retinaScale, height() * retinaScale);
|
||||||
|
|
||||||
|
QVector4D vec(x, y, 1, 1);
|
||||||
|
vec = vp.inverted() * vec;
|
||||||
|
vec = projection.inverted() * QVector4D(vec.x(), vec.y(), -1, 1);
|
||||||
|
|
||||||
|
// Hic sunt dracones.
|
||||||
|
// TODO(q3k): grab a book, remind yourselfl linear algebra and undo this
|
||||||
|
// operation properly.
|
||||||
|
QVector3D ray = vec.toVector3DAffine();
|
||||||
|
ray.normalize();
|
||||||
|
ray.setX((ray.x() / -ray.z()) * zoom_);
|
||||||
|
ray.setY((ray.y() / ray.z()) * zoom_);
|
||||||
|
ray.setZ(1.0);
|
||||||
|
|
||||||
|
vec = viewMove_.inverted() * QVector4D(ray.x(), ray.y(), ray.z(), 1.0);
|
||||||
|
vec.setZ(0);
|
||||||
|
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector4D FPGAViewWidget::mouseToWorldDimensions(float x, float y)
|
||||||
{
|
{
|
||||||
QMatrix4x4 p = getProjection();
|
QMatrix4x4 p = getProjection();
|
||||||
|
p.translate(0.0f, 0.0f, -zoom_);
|
||||||
QVector2D unit = p.map(QVector4D(1, 1, 0, 1)).toVector2DAffine();
|
QVector2D unit = p.map(QVector4D(1, 1, 0, 1)).toVector2DAffine();
|
||||||
|
|
||||||
float sx = (((float)x) / (width() / 2));
|
float sx = (((float)x) / (width() / 2));
|
||||||
@ -367,18 +693,6 @@ QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y)
|
|||||||
return QVector4D(sx / unit.x(), sy / unit.y(), 0, 1);
|
return QVector4D(sx / unit.x(), sy / unit.y(), 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event)
|
|
||||||
{
|
|
||||||
const int dx = event->x() - lastDragPos_.x();
|
|
||||||
const int dy = event->y() - lastDragPos_.y();
|
|
||||||
lastDragPos_ = event->pos();
|
|
||||||
|
|
||||||
auto world = mouseToWorldCoordinates(dx, dy);
|
|
||||||
viewMove_.translate(world.x(), -world.y());
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FPGAViewWidget::wheelEvent(QWheelEvent *event)
|
void FPGAViewWidget::wheelEvent(QWheelEvent *event)
|
||||||
{
|
{
|
||||||
QPoint degree = event->angleDelta() / 8;
|
QPoint degree = event->angleDelta() / 8;
|
||||||
@ -389,26 +703,64 @@ void FPGAViewWidget::wheelEvent(QWheelEvent *event)
|
|||||||
|
|
||||||
void FPGAViewWidget::zoom(int level)
|
void FPGAViewWidget::zoom(int level)
|
||||||
{
|
{
|
||||||
if (zoom_ < zoomNear_) {
|
if (zoom_ < zoomLvl1_) {
|
||||||
zoom_ = zoomNear_;
|
zoom_ -= level / 500.0;
|
||||||
} else if (zoom_ < zoomLvl1_) {
|
|
||||||
zoom_ -= level / 10.0;
|
|
||||||
} else if (zoom_ < zoomLvl2_) {
|
} else if (zoom_ < zoomLvl2_) {
|
||||||
zoom_ -= level / 5.0;
|
zoom_ -= level / 100.0;
|
||||||
} else if (zoom_ < zoomFar_) {
|
|
||||||
zoom_ -= level;
|
|
||||||
} else {
|
} else {
|
||||||
zoom_ = zoomFar_;
|
zoom_ -= level / 10.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (zoom_ < zoomNear_)
|
||||||
|
zoom_ = zoomNear_;
|
||||||
|
else if (zoom_ > zoomFar_)
|
||||||
|
zoom_ = zoomFar_;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FPGAViewWidget::clampZoom()
|
||||||
|
{
|
||||||
|
if (zoom_ < zoomNear_)
|
||||||
|
zoom_ = zoomNear_;
|
||||||
|
else if (zoom_ > zoomFar_)
|
||||||
|
zoom_ = zoomFar_;
|
||||||
|
}
|
||||||
|
|
||||||
void FPGAViewWidget::zoomIn() { zoom(10); }
|
void FPGAViewWidget::zoomIn() { zoom(10); }
|
||||||
|
|
||||||
void FPGAViewWidget::zoomOut() { zoom(-10); }
|
void FPGAViewWidget::zoomOut() { zoom(-10); }
|
||||||
|
|
||||||
void FPGAViewWidget::zoomSelected() {}
|
void FPGAViewWidget::zoomToBB(const PickQuadTree::BoundingBox &bb, float margin)
|
||||||
|
{
|
||||||
|
if (bb.w() < 0.00005 && bb.h() < 0.00005)
|
||||||
|
return;
|
||||||
|
|
||||||
void FPGAViewWidget::zoomOutbound() {}
|
viewMove_.setToIdentity();
|
||||||
|
viewMove_.translate(-(bb.x0() + bb.w() / 2), -(bb.y0() + bb.h() / 2));
|
||||||
|
|
||||||
|
// Our FOV is π/2, so distance for camera to see a plane of width H is H/2.
|
||||||
|
// We add 1 unit to cover half a unit of extra space around.
|
||||||
|
float distance_w = bb.w() / 2 + margin;
|
||||||
|
float distance_h = bb.h() / 2 + margin;
|
||||||
|
zoom_ = std::max(distance_w, distance_h);
|
||||||
|
clampZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPGAViewWidget::zoomSelected()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&rendererDataLock_);
|
||||||
|
zoomToBB(rendererData_->bbSelected, 0.5f);
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPGAViewWidget::zoomOutbound()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&rendererDataLock_);
|
||||||
|
zoomToBB(rendererData_->bbGlobal, 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -31,9 +31,12 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QWaitCondition>
|
#include <QWaitCondition>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
#include "nextpnr.h"
|
#include "designwidget.h"
|
||||||
#include "lineshader.h"
|
#include "lineshader.h"
|
||||||
|
#include "nextpnr.h"
|
||||||
|
#include "quadtree.h"
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@ -105,10 +108,9 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
|||||||
QSize minimumSizeHint() const override;
|
QSize minimumSizeHint() const override;
|
||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void newContext(Context *ctx);
|
void newContext(Context *ctx);
|
||||||
void onSelectedArchItem(std::vector<DecalXY> decals);
|
void onSelectedArchItem(std::vector<DecalXY> decals, bool keep);
|
||||||
void onHighlightGroupChanged(std::vector<DecalXY> decals, int group);
|
void onHighlightGroupChanged(std::vector<DecalXY> decals, int group);
|
||||||
void pokeRenderer(void);
|
void pokeRenderer(void);
|
||||||
void zoomIn();
|
void zoomIn();
|
||||||
@ -116,11 +118,103 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
|||||||
void zoomSelected();
|
void zoomSelected();
|
||||||
void zoomOutbound();
|
void zoomOutbound();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void clickedBel(BelId bel, bool add);
|
||||||
|
void clickedWire(WireId wire, bool add);
|
||||||
|
void clickedPip(PipId pip, bool add);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const float zoomNear_ = 1.0f; // do not zoom closer than this
|
const float zoomNear_ = 0.1f; // do not zoom closer than this
|
||||||
const float zoomFar_ = 10000.0f; // do not zoom further than this
|
const float zoomFar_ = 30.0f; // do not zoom further than this
|
||||||
const float zoomLvl1_ = 100.0f;
|
const float zoomLvl1_ = 1.0f;
|
||||||
const float zoomLvl2_ = 50.0f;
|
const float zoomLvl2_ = 5.0f;
|
||||||
|
|
||||||
|
struct PickedElement
|
||||||
|
{
|
||||||
|
ElementType type;
|
||||||
|
|
||||||
|
// These are not in an union (and thus this structure is very verbose
|
||||||
|
// and somewhat heavy) because the Id types are typedef'd to StringIds
|
||||||
|
// in the generic architecture. Once that changes (or we get an AnyId
|
||||||
|
// construct from Arches), this should go away.
|
||||||
|
BelId bel;
|
||||||
|
WireId wire;
|
||||||
|
PipId pip;
|
||||||
|
GroupId group;
|
||||||
|
|
||||||
|
float x, y; // Decal X and Y
|
||||||
|
|
||||||
|
PickedElement(ElementType type, float x, float y) : type(type), x(x), y(y) {}
|
||||||
|
|
||||||
|
static PickedElement fromBel(BelId bel, float x, float y)
|
||||||
|
{
|
||||||
|
PickedElement e(ElementType::BEL, x, y);
|
||||||
|
e.bel = bel;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
static PickedElement fromWire(WireId wire, float x, float y)
|
||||||
|
{
|
||||||
|
PickedElement e(ElementType::WIRE, x, y);
|
||||||
|
e.wire = wire;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
static PickedElement fromPip(PipId pip, float x, float y)
|
||||||
|
{
|
||||||
|
PickedElement e(ElementType::PIP, x, y);
|
||||||
|
e.pip = pip;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
static PickedElement fromGroup(GroupId group, float x, float y)
|
||||||
|
{
|
||||||
|
PickedElement e(ElementType::GROUP, x, y);
|
||||||
|
e.group = group;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
PickedElement(const PickedElement &other) : type(other.type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case ElementType::BEL:
|
||||||
|
bel = other.bel;
|
||||||
|
break;
|
||||||
|
case ElementType::WIRE:
|
||||||
|
wire = other.wire;
|
||||||
|
break;
|
||||||
|
case ElementType::PIP:
|
||||||
|
pip = other.pip;
|
||||||
|
break;
|
||||||
|
case ElementType::GROUP:
|
||||||
|
group = other.group;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
NPNR_ASSERT_FALSE("Invalid ElementType");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DecalXY decal(Context *ctx) const
|
||||||
|
{
|
||||||
|
DecalXY decal;
|
||||||
|
switch (type) {
|
||||||
|
case ElementType::BEL:
|
||||||
|
decal = ctx->getBelDecal(bel);
|
||||||
|
break;
|
||||||
|
case ElementType::WIRE:
|
||||||
|
decal = ctx->getWireDecal(wire);
|
||||||
|
break;
|
||||||
|
case ElementType::PIP:
|
||||||
|
decal = ctx->getPipDecal(pip);
|
||||||
|
break;
|
||||||
|
case ElementType::GROUP:
|
||||||
|
decal = ctx->getGroupDecal(group);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
NPNR_ASSERT_FALSE("Invalid ElementType");
|
||||||
|
}
|
||||||
|
return decal;
|
||||||
|
}
|
||||||
|
float distance(Context *ctx, float wx, float wy) const;
|
||||||
|
};
|
||||||
|
using PickQuadTree = QuadTree<float, PickedElement>;
|
||||||
|
|
||||||
Context *ctx_;
|
Context *ctx_;
|
||||||
QTimer paintTimer_;
|
QTimer paintTimer_;
|
||||||
@ -140,33 +234,75 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
|||||||
QColor inactive;
|
QColor inactive;
|
||||||
QColor active;
|
QColor active;
|
||||||
QColor selected;
|
QColor selected;
|
||||||
|
QColor hovered;
|
||||||
QColor highlight[8];
|
QColor highlight[8];
|
||||||
} colors_;
|
} 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() { zoomOutbound = false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RendererArgs
|
||||||
|
{
|
||||||
|
// Decals that he user selected.
|
||||||
|
std::vector<DecalXY> selectedDecals;
|
||||||
|
// Decals that the user highlighted.
|
||||||
|
std::vector<DecalXY> highlightedDecals[8];
|
||||||
|
// Decals that the user's mouse is hovering in.
|
||||||
|
DecalXY hoveredDecal;
|
||||||
|
// Whether to render the above three or skip it.
|
||||||
|
bool changed;
|
||||||
|
|
||||||
|
// Flags to pass back into the RendererData.
|
||||||
|
PassthroughFlags flags;
|
||||||
|
};
|
||||||
|
std::unique_ptr<RendererArgs> rendererArgs_;
|
||||||
|
QMutex rendererArgsLock_;
|
||||||
|
|
||||||
struct RendererData
|
struct RendererData
|
||||||
{
|
{
|
||||||
LineShaderData gfxByStyle[GraphicElement::STYLE_MAX];
|
LineShaderData gfxByStyle[GraphicElement::STYLE_MAX];
|
||||||
LineShaderData gfxSelected;
|
LineShaderData gfxSelected;
|
||||||
|
LineShaderData gfxHovered;
|
||||||
LineShaderData gfxHighlighted[8];
|
LineShaderData gfxHighlighted[8];
|
||||||
|
// Global bounding box of data from Arch.
|
||||||
|
PickQuadTree::BoundingBox bbGlobal;
|
||||||
|
// Bounding box of selected items.
|
||||||
|
PickQuadTree::BoundingBox bbSelected;
|
||||||
|
// Quadtree for picking objects.
|
||||||
|
std::unique_ptr<PickQuadTree> qt;
|
||||||
|
// Flags from args.
|
||||||
|
PassthroughFlags flags;
|
||||||
};
|
};
|
||||||
std::unique_ptr<RendererData> rendererData_;
|
std::unique_ptr<RendererData> rendererData_;
|
||||||
QMutex rendererDataLock_;
|
QMutex rendererDataLock_;
|
||||||
|
|
||||||
struct RendererArgs
|
void clampZoom();
|
||||||
{
|
void zoomToBB(const PickQuadTree::BoundingBox &bb, float margin);
|
||||||
std::vector<DecalXY> selectedDecals;
|
|
||||||
std::vector<DecalXY> highlightedDecals[8];
|
|
||||||
bool highlightedOrSelectedChanged;
|
|
||||||
};
|
|
||||||
std::unique_ptr<RendererArgs> rendererArgs_;
|
|
||||||
QMutex rendererArgsLock_;
|
|
||||||
|
|
||||||
void zoom(int level);
|
void zoom(int level);
|
||||||
void renderLines(void);
|
void renderLines(void);
|
||||||
void drawGraphicElement(LineShaderData &out, const GraphicElement &el, float x, float y);
|
void renderGraphicElement(LineShaderData &out, PickQuadTree::BoundingBox &bb, const GraphicElement &el, float x,
|
||||||
void drawDecal(LineShaderData &out, const DecalXY &decal);
|
float y);
|
||||||
void drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal);
|
void renderDecal(LineShaderData &out, PickQuadTree::BoundingBox &bb, const DecalXY &decal);
|
||||||
|
void renderArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], PickQuadTree::BoundingBox &bb,
|
||||||
|
const DecalXY &decal);
|
||||||
|
void populateQuadTree(RendererData *data, const DecalXY &decal, const PickedElement &element);
|
||||||
|
boost::optional<PickedElement> pickElement(float worldx, float worldy);
|
||||||
QVector4D mouseToWorldCoordinates(int x, int y);
|
QVector4D mouseToWorldCoordinates(int x, int y);
|
||||||
|
QVector4D mouseToWorldDimensions(float x, float y);
|
||||||
QMatrix4x4 getProjection(void);
|
QMatrix4x4 getProjection(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "log.h"
|
|
||||||
#include "lineshader.h"
|
#include "lineshader.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
425
gui/quadtree.h
Normal file
425
gui/quadtree.h
Normal file
@ -0,0 +1,425 @@
|
|||||||
|
/*
|
||||||
|
* nextpnr -- Next Generation Place and Route
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef QUADTREE_H
|
||||||
|
#define QUADTREE_H
|
||||||
|
|
||||||
|
// This file implements a quad tree used for comitting 2D axis aligned
|
||||||
|
// bounding boxes and then retrieving them by 2D point.
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
// A node of a QuadTree. Internal.
|
||||||
|
template <typename CoordinateT, typename ElementT> class QuadTreeNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class BoundingBox
|
||||||
|
{
|
||||||
|
friend class QuadTreeNode;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CoordinateT x0_, y0_, x1_, y1_;
|
||||||
|
|
||||||
|
static constexpr float pinf = std::numeric_limits<CoordinateT>::infinity();
|
||||||
|
static constexpr float ninf = -std::numeric_limits<CoordinateT>::infinity();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Standard constructor for a given (x0,y0), (x1,y1) bounding box
|
||||||
|
//
|
||||||
|
// @param x0 x coordinate of top-left corner of box
|
||||||
|
// @param y0 y coordinate of top-left corner of box
|
||||||
|
// @param x1 x coordinate of bottom-right corner of box
|
||||||
|
// @param y1 y coordinate of bottom-right corner of box
|
||||||
|
BoundingBox(CoordinateT x0, CoordinateT y0, CoordinateT x1, CoordinateT y1) : x0_(x0), y0_(y0), x1_(x1), y1_(y1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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_) {}
|
||||||
|
|
||||||
|
// Whether a bounding box contains a given points.
|
||||||
|
// A point is defined to be in a bounding box when it's not lesser than
|
||||||
|
// the lower coordinate or greater than the higher coordinate, eg:
|
||||||
|
// A BoundingBox of x0: 20, y0: 30, x1: 100, y1: 130 fits the following
|
||||||
|
// points:
|
||||||
|
// [ (50, 50), (20, 50), (20, 30), (100, 130) ]
|
||||||
|
inline bool contains(CoordinateT x, CoordinateT y) const
|
||||||
|
{
|
||||||
|
if (x < x0_ || x > x1_)
|
||||||
|
return false;
|
||||||
|
if (y < y0_ || y > y1_)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the bounding box coordinates.
|
||||||
|
void fixup()
|
||||||
|
{
|
||||||
|
if (x1_ < x0_)
|
||||||
|
std::swap(x0_, x1_);
|
||||||
|
if (y1_ < y0_)
|
||||||
|
std::swap(y0_, y1_);
|
||||||
|
}
|
||||||
|
|
||||||
|
CoordinateT x0() const { return x0_; }
|
||||||
|
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; }
|
||||||
|
void setY1(CoordinateT v) { y1_ = v; }
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
x0_ = pinf;
|
||||||
|
y0_ = pinf;
|
||||||
|
x1_ = ninf;
|
||||||
|
y1_ = ninf;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoordinateT w() const { return x1_ - x0_; }
|
||||||
|
CoordinateT h() const { return y1_ - y0_; }
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
// A pair of Element and BoundingBox that contains it.
|
||||||
|
class BoundElement
|
||||||
|
{
|
||||||
|
friend class QuadTreeNode;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BoundingBox bb_;
|
||||||
|
ElementT elem_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BoundElement(BoundingBox bb, ElementT elem) : bb_(bb), elem_(elem) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The bounding box that this node describes.
|
||||||
|
BoundingBox bound_;
|
||||||
|
// How many elements should be contained in this node until it splits into
|
||||||
|
// sub-nodes.
|
||||||
|
const size_t max_elems_;
|
||||||
|
// Four sub-nodes or nullptr if it hasn't split yet.
|
||||||
|
std::unique_ptr<QuadTreeNode<CoordinateT, ElementT>[]> children_ = nullptr;
|
||||||
|
// Coordinates of the split.
|
||||||
|
// Anything < split_x is west.
|
||||||
|
CoordinateT splitx_;
|
||||||
|
// Anything < split_y is north.
|
||||||
|
CoordinateT splity_;
|
||||||
|
|
||||||
|
// Elements contained directly within this node and not part of children
|
||||||
|
// nodes.
|
||||||
|
std::vector<BoundElement> elems_;
|
||||||
|
// Depth at which this node is - root is at 0, first level at 1, etc.
|
||||||
|
int depth_;
|
||||||
|
|
||||||
|
// Checks whether a given bounding box fits within this node - used for
|
||||||
|
// sanity checking on insertion.
|
||||||
|
// @param b bounding box to check
|
||||||
|
// @returns whether b fits in this node entirely
|
||||||
|
bool fits(const BoundingBox &b) const
|
||||||
|
{
|
||||||
|
if (b.x0_ < bound_.x0_ || b.x0_ > bound_.x1_) {
|
||||||
|
return false;
|
||||||
|
} else if (b.x1_ < bound_.x0_ || b.x1_ > bound_.x1_) {
|
||||||
|
return false;
|
||||||
|
} else if (b.y0_ < bound_.y0_ || b.y0_ > bound_.y1_) {
|
||||||
|
return false;
|
||||||
|
} else if (b.y1_ < bound_.y0_ || b.y1_ > bound_.y1_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to describe one of 5 possible places an element can exist:
|
||||||
|
// - the node itself (THIS)
|
||||||
|
// - any of the 4 children nodes.
|
||||||
|
enum Quadrant
|
||||||
|
{
|
||||||
|
THIS_NODE = -1,
|
||||||
|
NW = 0,
|
||||||
|
NE = 1,
|
||||||
|
SW = 2,
|
||||||
|
SE = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
// Finds the quadrant to which a bounding box should go (if the node
|
||||||
|
// is / were to be split).
|
||||||
|
// @param b bounding box to check
|
||||||
|
// @returns quadrant in which b belongs to if the node is were to be split
|
||||||
|
Quadrant quadrant(const BoundingBox &b) const
|
||||||
|
{
|
||||||
|
if (children_ == nullptr) {
|
||||||
|
return THIS_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool west0 = b.x0_ < splitx_;
|
||||||
|
bool west1 = b.x1_ < splitx_;
|
||||||
|
bool north0 = b.y0_ < splity_;
|
||||||
|
bool north1 = b.y1_ < splity_;
|
||||||
|
|
||||||
|
if (west0 && west1 && north0 && north1)
|
||||||
|
return NW;
|
||||||
|
if (!west0 && !west1 && north0 && north1)
|
||||||
|
return NE;
|
||||||
|
if (west0 && west1 && !north0 && !north1)
|
||||||
|
return SW;
|
||||||
|
if (!west0 && !west1 && !north0 && !north1)
|
||||||
|
return SE;
|
||||||
|
return THIS_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks whether this node should split.
|
||||||
|
bool should_split() const
|
||||||
|
{
|
||||||
|
// The node shouldn't split if it's not large enough to merit it.
|
||||||
|
if (elems_.size() < max_elems_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// The node shouldn't split if its' level is too deep (this is true for
|
||||||
|
// 100k+ entries, where the amount of splits causes us to lose
|
||||||
|
// significant CPU time on traversing the tree, or worse yet causes a
|
||||||
|
// stack overflow).
|
||||||
|
if (depth_ > 5)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Standard constructor for node.
|
||||||
|
// @param b BoundingBox this node covers.
|
||||||
|
// @param depth depth at which this node is in the tree
|
||||||
|
// @max_elems how many elements should this node contain before it splits
|
||||||
|
QuadTreeNode(BoundingBox b, int depth, size_t max_elems = 4) : bound_(b), max_elems_(max_elems), depth_(depth) {}
|
||||||
|
// Disallow copies.
|
||||||
|
QuadTreeNode(const QuadTreeNode &other) = delete;
|
||||||
|
QuadTreeNode &operator=(const QuadTreeNode &other) = delete;
|
||||||
|
// Allow moves.
|
||||||
|
QuadTreeNode(QuadTreeNode &&other)
|
||||||
|
: bound_(other.bound_), max_elems_(other.max_elems_), children_(std::move(other.children_)),
|
||||||
|
splitx_(other.splitx_), splity_(other.splity_), elems_(std::move(other.elems_)), depth_(other.depth_)
|
||||||
|
{
|
||||||
|
other.children_ = nullptr;
|
||||||
|
}
|
||||||
|
QuadTreeNode &operator=(QuadTreeNode &&other)
|
||||||
|
{
|
||||||
|
if (this == &other)
|
||||||
|
return *this;
|
||||||
|
bound_ = other.bound_;
|
||||||
|
max_elems_ = other.max_elems_;
|
||||||
|
children_ = other.max_children_;
|
||||||
|
children_ = other.children_;
|
||||||
|
splitx_ = other.splitx_;
|
||||||
|
splity_ = other.splity_;
|
||||||
|
elems_ = std::move(other.elems_);
|
||||||
|
depth_ = other.depth_;
|
||||||
|
other.children_ = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert an element at a given bounding box.
|
||||||
|
bool insert(const BoundingBox &k, ElementT v)
|
||||||
|
{
|
||||||
|
// Fail early if this BB doesn't fit us at all.
|
||||||
|
if (!fits(k)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have children?
|
||||||
|
if (children_ != nullptr) {
|
||||||
|
// Put the element either recursively into a child if it fits
|
||||||
|
// entirely or keep it for ourselves if not.
|
||||||
|
auto quad = quadrant(k);
|
||||||
|
if (quad == THIS_NODE) {
|
||||||
|
elems_.push_back(BoundElement(k, std::move(v)));
|
||||||
|
} else {
|
||||||
|
return children_[quad].insert(k, std::move(v));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No children and not about to have any.
|
||||||
|
if (!should_split()) {
|
||||||
|
elems_.push_back(BoundElement(k, std::move(v)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Splitting. Calculate the split point.
|
||||||
|
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_),
|
||||||
|
});
|
||||||
|
// Move all elements to where they belong.
|
||||||
|
auto it = elems_.begin();
|
||||||
|
while (it != elems_.end()) {
|
||||||
|
auto quad = quadrant(it->bb_);
|
||||||
|
if (quad != THIS_NODE) {
|
||||||
|
// Move to one of the children.
|
||||||
|
if (!children_[quad].insert(it->bb_, std::move(it->elem_)))
|
||||||
|
return false;
|
||||||
|
// Delete from ourselves.
|
||||||
|
it = elems_.erase(it);
|
||||||
|
} else {
|
||||||
|
// Keep for ourselves.
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Insert the actual element now that we've split.
|
||||||
|
return insert(k, std::move(v));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump a human-readable representation of the tree to stdout.
|
||||||
|
void dump(int level) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < level; i++)
|
||||||
|
printf(" ");
|
||||||
|
printf("loc: % 3d % 3d % 3d % 3d\n", bound_.x0_, bound_.y0_, bound_.x1_, bound_.y1_);
|
||||||
|
if (elems_.size() != 0) {
|
||||||
|
for (int i = 0; i < level; i++)
|
||||||
|
printf(" ");
|
||||||
|
printf("elems: %zu\n", elems_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children_ != nullptr) {
|
||||||
|
for (int i = 0; i < level; i++)
|
||||||
|
printf(" ");
|
||||||
|
printf("children:\n");
|
||||||
|
children_[NW].dump(level + 1);
|
||||||
|
children_[NE].dump(level + 1);
|
||||||
|
children_[SW].dump(level + 1);
|
||||||
|
children_[SE].dump(level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return count of BoundingBoxes/Elements contained.
|
||||||
|
// @returns count of elements contained.
|
||||||
|
size_t size() const
|
||||||
|
{
|
||||||
|
size_t res = elems_.size();
|
||||||
|
if (children_ != nullptr) {
|
||||||
|
res += children_[NW].size();
|
||||||
|
res += children_[NE].size();
|
||||||
|
res += children_[SW].size();
|
||||||
|
res += children_[SE].size();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve elements whose bounding boxes cover the given coordinates.
|
||||||
|
//
|
||||||
|
// @param x X coordinate of points to query.
|
||||||
|
// @param y Y coordinate of points to query.
|
||||||
|
// @returns vector of found bounding boxes
|
||||||
|
void get(CoordinateT x, CoordinateT y, std::vector<ElementT> &res) const
|
||||||
|
{
|
||||||
|
if (!bound_.contains(x, y))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const auto &elem : elems_) {
|
||||||
|
const auto &bb = elem.bb_;
|
||||||
|
if (bb.contains(x, y)) {
|
||||||
|
res.push_back(elem.elem_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (children_ != nullptr) {
|
||||||
|
children_[NW].get(x, y, res);
|
||||||
|
children_[NE].get(x, y, res);
|
||||||
|
children_[SW].get(x, y, res);
|
||||||
|
children_[SE].get(x, y, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// User facing method to manage a quad tree.
|
||||||
|
//
|
||||||
|
// @param CoodinateT scalar type of the coordinate system - int, float, ...
|
||||||
|
// @param ElementT type of the contained element. Must be movable or copiable.
|
||||||
|
template <typename CoordinateT, typename ElementT> class QuadTree
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// Root of the tree.
|
||||||
|
QuadTreeNode<CoordinateT, ElementT> root_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// To let user create bounding boxes of the correct type.
|
||||||
|
// Bounding boxes are composed of two 2D points, which designate their
|
||||||
|
// top-left and bottom-right corners. All its' edges are axis aligned.
|
||||||
|
using BoundingBox = typename QuadTreeNode<CoordinateT, ElementT>::BoundingBox;
|
||||||
|
|
||||||
|
// Standard constructor.
|
||||||
|
//
|
||||||
|
// @param b Bounding box of the entire tree - all comitted elements must
|
||||||
|
// fit within in.
|
||||||
|
QuadTree(BoundingBox b) : root_(b, 0) {}
|
||||||
|
|
||||||
|
// Inserts a new value at a given bounding box.e
|
||||||
|
// BoundingBoxes are not deduplicated - if two are pushed with the same
|
||||||
|
// coordinates, the first one will take precendence.
|
||||||
|
//
|
||||||
|
// @param k Bounding box at which to store value.
|
||||||
|
// @param v Value at a given bounding box.
|
||||||
|
// @returns Whether the insert was succesful.
|
||||||
|
bool insert(BoundingBox k, ElementT v)
|
||||||
|
{
|
||||||
|
k.fixup();
|
||||||
|
return root_.insert(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump a human-readable representation of the tree to stdout.
|
||||||
|
void dump() const { root_.dump(0); }
|
||||||
|
|
||||||
|
// Return count of BoundingBoxes/Elements contained.
|
||||||
|
// @returns count of elements contained.
|
||||||
|
size_t size() const { return root_.size(); }
|
||||||
|
|
||||||
|
// Retrieve elements whose bounding boxes cover the given coordinates.
|
||||||
|
//
|
||||||
|
// @param x X coordinate of points to query.
|
||||||
|
// @param y Y coordinate of points to query.
|
||||||
|
// @returns vector of found bounding boxes
|
||||||
|
std::vector<ElementT> get(CoordinateT x, CoordinateT y) const
|
||||||
|
{
|
||||||
|
std::vector<ElementT> res;
|
||||||
|
root_.get(x, y, res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
|
#endif
|
@ -263,21 +263,6 @@ void vcc_net(Context *ctx, NetInfo *net)
|
|||||||
ctx->cells[cell->name] = std::move(cell);
|
ctx->cells[cell->name] = std::move(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
void floating_net(Context *ctx, NetInfo *net)
|
|
||||||
{
|
|
||||||
PortInfo port_info;
|
|
||||||
PortRef port_ref;
|
|
||||||
|
|
||||||
port_info.name = ctx->id(net->name.str(ctx) + ".floating");
|
|
||||||
port_info.net = net;
|
|
||||||
port_info.type = PORT_OUT;
|
|
||||||
|
|
||||||
port_ref.cell = NULL;
|
|
||||||
port_ref.port = port_info.name;
|
|
||||||
|
|
||||||
net->driver = port_ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// is_blackbox
|
// is_blackbox
|
||||||
//
|
//
|
||||||
@ -475,10 +460,10 @@ void json_import_ports(Context *ctx, const string &modname, const std::vector<Id
|
|||||||
|
|
||||||
} else if (wire_node->data_string.compare(string("x")) == 0) {
|
} else if (wire_node->data_string.compare(string("x")) == 0) {
|
||||||
|
|
||||||
floating_net(ctx, net.get());
|
ground_net(ctx, net.get());
|
||||||
log_warning(" Floating wire node value, "
|
log_info(" Floating wire node value, "
|
||||||
"\'%s\' of port \'%s\' "
|
"\'%s\' of port \'%s\' "
|
||||||
"in cell \'%s\' of module \'%s\'\n",
|
"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());
|
wire_node->data_string.c_str(), port_name.c_str(), obj_name.c_str(), modname.c_str());
|
||||||
|
|
||||||
} else
|
} else
|
||||||
|
127
tests/gui/quadtree.cc
Normal file
127
tests/gui/quadtree.cc
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* nextpnr -- Next Generation Place and Route
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "nextpnr.h"
|
||||||
|
#include "quadtree.h"
|
||||||
|
|
||||||
|
USING_NEXTPNR_NAMESPACE
|
||||||
|
|
||||||
|
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_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width_ = 100;
|
||||||
|
int height_ = 100;
|
||||||
|
QT *qt_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test that we're doing bound checking correctly.
|
||||||
|
TEST_F(QuadTreeTest, insert_bound_checking)
|
||||||
|
{
|
||||||
|
ASSERT_TRUE(qt_->insert(QT::BoundingBox(10, 10, 20, 20), 10));
|
||||||
|
ASSERT_TRUE(qt_->insert(QT::BoundingBox(0, 0, 100, 100), 10));
|
||||||
|
ASSERT_FALSE(qt_->insert(QT::BoundingBox(10, 10, 101, 20), 10));
|
||||||
|
ASSERT_FALSE(qt_->insert(QT::BoundingBox(-1, 10, 101, 20), 10));
|
||||||
|
ASSERT_FALSE(qt_->insert(QT::BoundingBox(-1, -1, 20, 20), 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test whether we are not losing any elements.
|
||||||
|
TEST_F(QuadTreeTest, insert_count)
|
||||||
|
{
|
||||||
|
auto rng = NEXTPNR_NAMESPACE::DeterministicRNG();
|
||||||
|
|
||||||
|
// Add 10000 random rectangles.
|
||||||
|
for (unsigned int i = 0; i < 10000; i++) {
|
||||||
|
int x0 = rng.rng(width_);
|
||||||
|
int y0 = rng.rng(height_);
|
||||||
|
int w = rng.rng(width_ - x0);
|
||||||
|
int h = rng.rng(width_ - y0);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
// Add 100000 random points.
|
||||||
|
for (unsigned int i = 0; i < 100000; i++) {
|
||||||
|
int x0 = rng.rng(width_);
|
||||||
|
int y0 = rng.rng(height_);
|
||||||
|
int x1 = x0;
|
||||||
|
int y1 = y0;
|
||||||
|
ASSERT_TRUE(qt_->insert(QT::BoundingBox(x0, y0, x1, y1), i));
|
||||||
|
ASSERT_EQ(qt_->size(), i+10001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that we can insert and retrieve the same element.
|
||||||
|
TEST_F(QuadTreeTest, insert_retrieve_same)
|
||||||
|
{
|
||||||
|
auto rng = NEXTPNR_NAMESPACE::DeterministicRNG();
|
||||||
|
|
||||||
|
// Add 10000 small random rectangles.
|
||||||
|
rng.rngseed(0);
|
||||||
|
for (int i = 0; i < 10000; i++) {
|
||||||
|
int x0 = rng.rng(width_);
|
||||||
|
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;
|
||||||
|
ASSERT_TRUE(qt_->insert(QT::BoundingBox(x0, y0, x1, y1), i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart RNG, make sure we get the same rectangles back.
|
||||||
|
rng.rngseed(0);
|
||||||
|
for (int i = 0; i < 10000; i++) {
|
||||||
|
int x0 = rng.rng(width_);
|
||||||
|
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;
|
||||||
|
|
||||||
|
// try to find something in the middle of the square
|
||||||
|
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
|
||||||
|
// amounts of data.
|
||||||
|
ASSERT_LT(res.size(), 200UL);
|
||||||
|
bool found = false;
|
||||||
|
for (auto elem : res) {
|
||||||
|
// Is this what we're looking for?
|
||||||
|
if (elem == i) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(found);
|
||||||
|
}
|
||||||
|
}
|
@ -59,7 +59,7 @@ TEST_F(HX1KTest, wire_names)
|
|||||||
assert(wire == ctx->getWireByName(name));
|
assert(wire == ctx->getWireByName(name));
|
||||||
wire_count++;
|
wire_count++;
|
||||||
}
|
}
|
||||||
ASSERT_EQ(wire_count, 27682);
|
ASSERT_EQ(wire_count, 27690);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(HX1KTest, pip_names)
|
TEST_F(HX1KTest, pip_names)
|
||||||
|
@ -59,7 +59,7 @@ TEST_F(HX8KTest, wire_names)
|
|||||||
assert(wire == ctx->getWireByName(name));
|
assert(wire == ctx->getWireByName(name));
|
||||||
wire_count++;
|
wire_count++;
|
||||||
}
|
}
|
||||||
ASSERT_EQ(wire_count, 135174);
|
ASSERT_EQ(wire_count, 135182);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(HX8KTest, pip_names)
|
TEST_F(HX8KTest, pip_names)
|
||||||
|
@ -59,7 +59,7 @@ TEST_F(LP1KTest, wire_names)
|
|||||||
assert(wire == ctx->getWireByName(name));
|
assert(wire == ctx->getWireByName(name));
|
||||||
wire_count++;
|
wire_count++;
|
||||||
}
|
}
|
||||||
ASSERT_EQ(wire_count, 27682);
|
ASSERT_EQ(wire_count, 27690);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LP1KTest, pip_names)
|
TEST_F(LP1KTest, pip_names)
|
||||||
|
@ -59,7 +59,7 @@ TEST_F(LP384Test, wire_names)
|
|||||||
assert(wire == ctx->getWireByName(name));
|
assert(wire == ctx->getWireByName(name));
|
||||||
wire_count++;
|
wire_count++;
|
||||||
}
|
}
|
||||||
ASSERT_EQ(wire_count, 8294);
|
ASSERT_EQ(wire_count, 8302);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LP384Test, pip_names)
|
TEST_F(LP384Test, pip_names)
|
||||||
|
@ -59,7 +59,7 @@ TEST_F(LP8KTest, wire_names)
|
|||||||
assert(wire == ctx->getWireByName(name));
|
assert(wire == ctx->getWireByName(name));
|
||||||
wire_count++;
|
wire_count++;
|
||||||
}
|
}
|
||||||
ASSERT_EQ(wire_count, 135174);
|
ASSERT_EQ(wire_count, 135182);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LP8KTest, pip_names)
|
TEST_F(LP8KTest, pip_names)
|
||||||
|
@ -59,7 +59,7 @@ TEST_F(UP5KTest, wire_names)
|
|||||||
assert(wire == ctx->getWireByName(name));
|
assert(wire == ctx->getWireByName(name));
|
||||||
wire_count++;
|
wire_count++;
|
||||||
}
|
}
|
||||||
ASSERT_EQ(wire_count, 103383);
|
ASSERT_EQ(wire_count, 103391);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UP5KTest, pip_names)
|
TEST_F(UP5KTest, pip_names)
|
||||||
|
Loading…
Reference in New Issue
Block a user