nextpnr/gui/designwidget.cc
D. Shah c10238de8c ice40: Switch from RelPtr to RelSlice
This replaces RelPtrs and a separate length field with a Rust-style
slice containing both a pointer and a length; with bounds checking
always enforced.

Thus iterating over these structures is both cleaner and safer.

Signed-off-by: D. Shah <dave@ds0.me>
2021-01-27 18:35:49 +00:00

1071 lines
43 KiB
C++

/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
* Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* 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 "designwidget.h"
#include <QAction>
#include <QApplication>
#include <QGridLayout>
#include <QLineEdit>
#include <QMenu>
#include <QSplitter>
#include <QToolBar>
#include <QTreeWidgetItem>
#include "fpgaviewwidget.h"
NEXTPNR_NAMESPACE_BEGIN
TreeView::TreeView(QWidget *parent) : QTreeView(parent) {}
TreeView::~TreeView() {}
void TreeView::mouseMoveEvent(QMouseEvent *event)
{
QModelIndex index = indexAt(event->pos());
if (index != current) {
current = index;
Q_EMIT hoverIndexChanged(index);
}
QTreeView::mouseMoveEvent(event);
}
void TreeView::leaveEvent(QEvent *event) { Q_EMIT hoverIndexChanged(QModelIndex()); }
DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr)
{
tabWidget = new QTabWidget();
// Add tree view
for (int i = 0; i < 6; i++) {
treeView[i] = new TreeView();
treeModel[i] = new TreeModel::Model();
treeView[i]->setModel(treeModel[i]);
treeView[i]->setContextMenuPolicy(Qt::CustomContextMenu);
treeView[i]->setSelectionMode(QAbstractItemView::ExtendedSelection);
treeView[i]->viewport()->setMouseTracking(true);
selectionModel[i] = nullptr;
}
tabWidget->addTab(treeView[0], "Bels");
tabWidget->addTab(treeView[1], "Wires");
tabWidget->addTab(treeView[2], "Pips");
tabWidget->addTab(treeView[3], "Cells");
tabWidget->addTab(treeView[4], "Nets");
tabWidget->addTab(treeView[5], "Groups");
// Add property view
variantManager = new QtVariantPropertyManager(this);
readOnlyManager = new QtVariantPropertyManager(this);
groupManager = new QtGroupPropertyManager(this);
variantFactory = new QtVariantEditorFactory(this);
propertyEditor = new QtTreePropertyBrowser(this);
propertyEditor->setFactoryForManager(variantManager, variantFactory);
propertyEditor->setPropertiesWithoutValueMarked(true);
propertyEditor->show();
propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu);
propertyEditor->treeWidget()->setSelectionMode(QAbstractItemView::ExtendedSelection);
propertyEditor->treeWidget()->viewport()->setMouseTracking(true);
searchEdit = new QLineEdit();
searchEdit->setClearButtonEnabled(true);
searchEdit->addAction(QIcon(":/icons/resources/zoom.png"), QLineEdit::LeadingPosition);
searchEdit->setPlaceholderText("Search...");
connect(searchEdit, &QLineEdit::returnPressed, this, &DesignWidget::onSearchInserted);
actionFirst = new QAction("", this);
actionFirst->setIcon(QIcon(":/icons/resources/resultset_first.png"));
actionFirst->setEnabled(false);
connect(actionFirst, &QAction::triggered, this, [this] {
history_ignore = true;
history_index = 0;
auto h = history.at(history_index);
if (tabWidget->currentIndex() != h.first) {
selectionModel[tabWidget->currentIndex()]->clearSelection();
tabWidget->setCurrentIndex(h.first);
selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select);
} else {
selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect);
}
updateButtons();
});
actionPrev = new QAction("", this);
actionPrev->setIcon(QIcon(":/icons/resources/resultset_previous.png"));
actionPrev->setEnabled(false);
connect(actionPrev, &QAction::triggered, this, [this] {
history_ignore = true;
history_index--;
auto h = history.at(history_index);
if (tabWidget->currentIndex() != h.first) {
selectionModel[tabWidget->currentIndex()]->clearSelection();
tabWidget->setCurrentIndex(h.first);
selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select);
} else {
selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect);
}
updateButtons();
});
actionNext = new QAction("", this);
actionNext->setIcon(QIcon(":/icons/resources/resultset_next.png"));
actionNext->setEnabled(false);
connect(actionNext, &QAction::triggered, this, [this] {
history_ignore = true;
history_index++;
auto h = history.at(history_index);
if (tabWidget->currentIndex() != h.first) {
selectionModel[tabWidget->currentIndex()]->clearSelection();
tabWidget->setCurrentIndex(h.first);
selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select);
} else {
selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect);
}
updateButtons();
});
actionLast = new QAction("", this);
actionLast->setIcon(QIcon(":/icons/resources/resultset_last.png"));
actionLast->setEnabled(false);
connect(actionLast, &QAction::triggered, this, [this] {
history_ignore = true;
history_index = int(history.size() - 1);
auto h = history.at(history_index);
if (tabWidget->currentIndex() != h.first) {
selectionModel[tabWidget->currentIndex()]->clearSelection();
tabWidget->setCurrentIndex(h.first);
selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select);
} else {
selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect);
}
updateButtons();
});
actionClear = new QAction("", this);
actionClear->setIcon(QIcon(":/icons/resources/cross.png"));
actionClear->setEnabled(true);
connect(actionClear, &QAction::triggered, this, [this] {
history_index = -1;
history.clear();
int num = tabWidget->currentIndex();
if (selectionModel[num]->selectedIndexes().size() > 0) {
QModelIndex index = selectionModel[num]->selectedIndexes().at(0);
if (index.isValid()) {
ElementType type = treeModel[num]->nodeFromIndex(index)->type();
if (type != ElementType::NONE)
addToHistory(num, index);
}
}
updateButtons();
});
QToolBar *toolbar = new QToolBar();
toolbar->addAction(actionFirst);
toolbar->addAction(actionPrev);
toolbar->addAction(actionNext);
toolbar->addAction(actionLast);
toolbar->addAction(actionClear);
QWidget *topWidget = new QWidget();
QVBoxLayout *vbox1 = new QVBoxLayout();
topWidget->setLayout(vbox1);
vbox1->setSpacing(5);
vbox1->setContentsMargins(0, 0, 0, 0);
vbox1->addWidget(searchEdit);
vbox1->addWidget(tabWidget);
QWidget *toolbarWidget = new QWidget();
QHBoxLayout *hbox = new QHBoxLayout;
hbox->setAlignment(Qt::AlignCenter);
toolbarWidget->setLayout(hbox);
hbox->setSpacing(0);
hbox->setContentsMargins(0, 0, 0, 0);
hbox->addWidget(toolbar);
QWidget *btmWidget = new QWidget();
QVBoxLayout *vbox2 = new QVBoxLayout();
btmWidget->setLayout(vbox2);
vbox2->setSpacing(0);
vbox2->setContentsMargins(0, 0, 0, 0);
vbox2->addWidget(toolbarWidget);
vbox2->addWidget(propertyEditor);
QSplitter *splitter = new QSplitter(Qt::Vertical);
splitter->addWidget(topWidget);
splitter->addWidget(btmWidget);
QGridLayout *mainLayout = new QGridLayout();
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addWidget(splitter);
setLayout(mainLayout);
// Connection
connect(propertyEditor->treeWidget(), &QTreeWidget::customContextMenuRequested, this,
&DesignWidget::prepareMenuProperty);
connect(propertyEditor->treeWidget(), &QTreeWidget::itemDoubleClicked, this, &DesignWidget::onItemDoubleClicked);
connect(propertyEditor, &QtTreePropertyBrowser::hoverPropertyChanged, this, &DesignWidget::onHoverPropertyChanged);
for (int num = 0; num < 6; num++) {
connect(treeView[num], &TreeView::customContextMenuRequested,
[this, num](const QPoint &pos) { prepareMenuTree(num, pos); });
connect(treeView[num], &TreeView::doubleClicked, [this](const QModelIndex &index) { onDoubleClicked(index); });
connect(treeView[num], &TreeView::hoverIndexChanged,
[this, num](QModelIndex index) { onHoverIndexChanged(num, index); });
selectionModel[num] = treeView[num]->selectionModel();
connect(selectionModel[num], &QItemSelectionModel::selectionChanged,
[this, num](const QItemSelection &selected, const QItemSelection &deselected) {
onSelectionChanged(num, selected, deselected);
});
}
history_index = -1;
history_ignore = false;
highlightColors[0] = QColor("#6495ed");
highlightColors[1] = QColor("#7fffd4");
highlightColors[2] = QColor("#98fb98");
highlightColors[3] = QColor("#ffd700");
highlightColors[4] = QColor("#cd5c5c");
highlightColors[5] = QColor("#fa8072");
highlightColors[6] = QColor("#ff69b4");
highlightColors[7] = QColor("#da70d6");
}
DesignWidget::~DesignWidget() {}
void DesignWidget::updateButtons()
{
int count = int(history.size());
actionFirst->setEnabled(history_index > 0);
actionPrev->setEnabled(history_index > 0);
actionNext->setEnabled(history_index < (count - 1));
actionLast->setEnabled(history_index < (count - 1));
}
void DesignWidget::addToHistory(int tab, QModelIndex item)
{
if (!history_ignore) {
int count = int(history.size());
for (int i = count - 1; i > history_index; i--)
history.pop_back();
history.push_back(std::make_pair(tab, item));
history_index++;
}
history_ignore = false;
updateButtons();
}
void DesignWidget::newContext(Context *ctx)
{
if (!ctx)
return;
highlightSelected.clear();
history_ignore = false;
history_index = -1;
history.clear();
updateButtons();
highlightSelected.clear();
this->ctx = ctx;
{
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
{
TreeModel::ElementXYRoot<BelId>::ElementMap belMap;
for (const auto &bel : ctx->getBels()) {
auto loc = ctx->getBelLocation(bel);
belMap[std::pair<int, int>(loc.x, loc.y)].push_back(bel);
}
auto belGetter = [](Context *ctx, BelId id) { return ctx->getBelName(id); };
getTreeByElementType(ElementType::BEL)
->loadData(ctx,
std::unique_ptr<TreeModel::ElementXYRoot<BelId>>(
new TreeModel::ElementXYRoot<BelId>(ctx, belMap, belGetter, ElementType::BEL)));
}
{
TreeModel::ElementXYRoot<WireId>::ElementMap wireMap;
#ifdef ARCH_ICE40
for (int i = 0; i < int(ctx->chip_info->wire_data.size()); i++) {
const auto wire = &ctx->chip_info->wire_data[i];
WireId wireid;
wireid.index = i;
wireMap[std::pair<int, int>(wire->x, wire->y)].push_back(wireid);
}
#endif
#ifdef ARCH_ECP5
for (const auto &wire : ctx->getWires()) {
wireMap[std::pair<int, int>(wire.location.x, wire.location.y)].push_back(wire);
}
#endif
auto wireGetter = [](Context *ctx, WireId id) { return ctx->getWireName(id); };
getTreeByElementType(ElementType::WIRE)
->loadData(ctx,
std::unique_ptr<TreeModel::ElementXYRoot<WireId>>(new TreeModel::ElementXYRoot<WireId>(
ctx, wireMap, wireGetter, ElementType::WIRE)));
}
{
TreeModel::ElementXYRoot<PipId>::ElementMap pipMap;
for (const auto &pip : ctx->getPips()) {
auto loc = ctx->getPipLocation(pip);
pipMap[std::pair<int, int>(loc.x, loc.y)].push_back(pip);
}
auto pipGetter = [](Context *ctx, PipId id) { return ctx->getPipName(id); };
getTreeByElementType(ElementType::PIP)
->loadData(ctx,
std::unique_ptr<TreeModel::ElementXYRoot<PipId>>(
new TreeModel::ElementXYRoot<PipId>(ctx, pipMap, pipGetter, ElementType::PIP)));
}
getTreeByElementType(ElementType::CELL)
->loadData(ctx,
std::unique_ptr<TreeModel::IdStringList>(new TreeModel::IdStringList(ElementType::CELL)));
getTreeByElementType(ElementType::NET)
->loadData(ctx,
std::unique_ptr<TreeModel::IdStringList>(new TreeModel::IdStringList(ElementType::NET)));
}
updateTree();
}
void DesignWidget::updateTree()
{
clearProperties();
QMap<TreeModel::Item *, int>::iterator i = highlightSelected.begin();
while (i != highlightSelected.end()) {
QMap<TreeModel::Item *, int>::iterator prev = i;
++i;
if (prev.key()->type() == ElementType::NET && ctx->nets.find(prev.key()->id()) == ctx->nets.end()) {
highlightSelected.erase(prev);
}
if (prev.key()->type() == ElementType::CELL && ctx->cells.find(prev.key()->id()) == ctx->cells.end()) {
highlightSelected.erase(prev);
}
}
{
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
std::vector<IdString> cells;
for (auto &pair : ctx->cells) {
cells.push_back(pair.first);
}
std::vector<IdString> nets;
for (auto &pair : ctx->nets) {
nets.push_back(pair.first);
}
getTreeByElementType(ElementType::CELL)->updateElements(cells);
getTreeByElementType(ElementType::NET)->updateElements(nets);
}
}
QtProperty *DesignWidget::addTopLevelProperty(const QString &id)
{
QtProperty *topItem = groupManager->addProperty(id);
propertyToId[topItem] = id;
idToProperty[id] = topItem;
topItem->setSelectable(false);
propertyEditor->addProperty(topItem);
return topItem;
}
void DesignWidget::clearProperties()
{
QMap<QtProperty *, QString>::ConstIterator itProp = propertyToId.constBegin();
while (itProp != propertyToId.constEnd()) {
delete itProp.key();
itProp++;
}
propertyToId.clear();
idToProperty.clear();
}
QString DesignWidget::getElementTypeName(ElementType type)
{
if (type == ElementType::NONE)
return "";
if (type == ElementType::BEL)
return "BEL";
if (type == ElementType::WIRE)
return "WIRE";
if (type == ElementType::PIP)
return "PIP";
if (type == ElementType::NET)
return "NET";
if (type == ElementType::CELL)
return "CELL";
return "";
}
ElementType DesignWidget::getElementTypeByName(QString type)
{
if (type == "BEL")
return ElementType::BEL;
if (type == "WIRE")
return ElementType::WIRE;
if (type == "PIP")
return ElementType::PIP;
if (type == "NET")
return ElementType::NET;
if (type == "CELL")
return ElementType::CELL;
return ElementType::NONE;
}
TreeModel::Model *DesignWidget::getTreeByElementType(ElementType type)
{
if (type == ElementType::NONE)
return nullptr;
if (type == ElementType::BEL)
return treeModel[0];
if (type == ElementType::WIRE)
return treeModel[1];
if (type == ElementType::PIP)
return treeModel[2];
if (type == ElementType::CELL)
return treeModel[3];
if (type == ElementType::NET)
return treeModel[4];
return nullptr;
}
int DesignWidget::getIndexByElementType(ElementType type)
{
if (type == ElementType::NONE)
return -1;
if (type == ElementType::BEL)
return 0;
if (type == ElementType::WIRE)
return 1;
if (type == ElementType::PIP)
return 2;
if (type == ElementType::CELL)
return 3;
if (type == ElementType::NET)
return 4;
if (type == ElementType::GROUP)
return 5;
return -1;
}
void DesignWidget::addProperty(QtProperty *topItem, int propertyType, const QString &name, QVariant value,
const ElementType &type)
{
QtVariantProperty *item = readOnlyManager->addProperty(propertyType, name);
item->setValue(value);
item->setPropertyId(getElementTypeName(type));
item->setSelectable(type != ElementType::NONE);
topItem->addSubProperty(item);
}
QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name)
{
QtProperty *item = groupManager->addProperty(name);
item->setSelectable(false);
topItem->addSubProperty(item);
return item;
}
void DesignWidget::clearAllSelectionModels()
{
for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++)
selectionModel[i]->clearSelection();
}
void DesignWidget::onClickedBel(BelId bel, bool keep)
{
boost::optional<TreeModel::Item *> item;
{
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
item = getTreeByElementType(ElementType::BEL)->nodeForId(ctx->getBelName(bel));
if (!item)
return;
Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep);
}
int index = getIndexByElementType(ElementType::BEL);
if (!keep)
clearAllSelectionModels();
if (tabWidget->currentIndex() != index) {
tabWidget->setCurrentIndex(index);
}
selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::BEL)->indexFromNode(*item),
keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
}
void DesignWidget::onClickedWire(WireId wire, bool keep)
{
boost::optional<TreeModel::Item *> item;
{
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
item = getTreeByElementType(ElementType::WIRE)->nodeForId(ctx->getWireName(wire));
if (!item)
return;
Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep);
}
int index = getIndexByElementType(ElementType::WIRE);
if (!keep)
clearAllSelectionModels();
if (tabWidget->currentIndex() != index)
tabWidget->setCurrentIndex(index);
selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::WIRE)->indexFromNode(*item),
keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
}
void DesignWidget::onClickedPip(PipId pip, bool keep)
{
boost::optional<TreeModel::Item *> item;
{
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
item = getTreeByElementType(ElementType::PIP)->nodeForId(ctx->getPipName(pip));
if (!item)
return;
Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep);
}
int index = getIndexByElementType(ElementType::PIP);
if (!keep)
clearAllSelectionModels();
if (tabWidget->currentIndex() != index)
tabWidget->setCurrentIndex(index);
selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::PIP)->indexFromNode(*item),
keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect);
}
void DesignWidget::onSelectionChanged(int num, const QItemSelection &, const QItemSelection &)
{
int num_selected = 0;
std::vector<DecalXY> decals;
for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) {
num_selected += selectionModel[i]->selectedIndexes().size();
for (auto index : selectionModel[i]->selectedIndexes()) {
TreeModel::Item *item = treeModel[i]->nodeFromIndex(index);
std::vector<DecalXY> d = getDecals(item->type(), item->id());
std::move(d.begin(), d.end(), std::back_inserter(decals));
}
}
// Keep other tree seleciton only if Control is pressed
if (num_selected > 1 && QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) == true) {
Q_EMIT selected(decals, false);
return;
}
// For deselect and multiple select just send all
if (selectionModel[num]->selectedIndexes().size() != 1) {
Q_EMIT selected(decals, false);
return;
}
QModelIndex index = selectionModel[num]->selectedIndexes().at(0);
if (!index.isValid())
return;
TreeModel::Item *clickItem = treeModel[num]->nodeFromIndex(index);
ElementType type = clickItem->type();
if (type == ElementType::NONE)
return;
// Clear other tab selections
for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++)
if (i != num)
selectionModel[i]->clearSelection();
addToHistory(num, index);
clearProperties();
IdString c = clickItem->id();
Q_EMIT selected(getDecals(type, c), false);
if (type == ElementType::BEL) {
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
BelId bel = ctx->getBelByName(c);
QtProperty *topItem = addTopLevelProperty("Bel");
addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
addProperty(topItem, QVariant::String, "Type", ctx->getBelType(bel).c_str(ctx));
addProperty(topItem, QVariant::Bool, "Available", ctx->checkBelAvail(bel));
addProperty(topItem, QVariant::String, "Bound Cell", ctx->nameOf(ctx->getBoundBelCell(bel)), ElementType::CELL);
addProperty(topItem, QVariant::String, "Conflicting Cell", ctx->nameOf(ctx->getConflictingBelCell(bel)),
ElementType::CELL);
QtProperty *attrsItem = addSubGroup(topItem, "Attributes");
for (auto &item : ctx->getBelAttrs(bel)) {
addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str());
}
QtProperty *belpinsItem = addSubGroup(topItem, "Ports");
for (const auto &item : ctx->getBelPins(bel)) {
QtProperty *portInfoItem = addSubGroup(belpinsItem, item.c_str(ctx));
addProperty(portInfoItem, QVariant::String, "Name", item.c_str(ctx));
addProperty(portInfoItem, QVariant::Int, "Type", int(ctx->getBelPinType(bel, item)));
WireId wire = ctx->getBelPinWire(bel, item);
addProperty(portInfoItem, QVariant::String, "Wire", ctx->getWireName(wire).c_str(ctx), ElementType::WIRE);
}
} else if (type == ElementType::WIRE) {
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
WireId wire = ctx->getWireByName(c);
QtProperty *topItem = addTopLevelProperty("Wire");
addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
addProperty(topItem, QVariant::String, "Type", ctx->getWireType(wire).c_str(ctx));
addProperty(topItem, QVariant::Bool, "Available", ctx->checkWireAvail(wire));
addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundWireNet(wire)), ElementType::NET);
addProperty(topItem, QVariant::String, "Conflicting Wire",
ctx->getWireName(ctx->getConflictingWireWire(wire)).c_str(ctx), ElementType::WIRE);
addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingWireNet(wire)),
ElementType::NET);
QtProperty *attrsItem = addSubGroup(topItem, "Attributes");
for (auto &item : ctx->getWireAttrs(wire)) {
addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str());
}
DelayInfo delay = ctx->getWireDelay(wire);
QtProperty *delayItem = addSubGroup(topItem, "Delay");
addProperty(delayItem, QVariant::Double, "Min Raise", delay.minRaiseDelay());
addProperty(delayItem, QVariant::Double, "Max Raise", delay.maxRaiseDelay());
addProperty(delayItem, QVariant::Double, "Min Fall", delay.minFallDelay());
addProperty(delayItem, QVariant::Double, "Max Fall", delay.maxFallDelay());
QtProperty *belpinsItem = addSubGroup(topItem, "BelPins");
for (const auto &item : ctx->getWireBelPins(wire)) {
QString belname = "";
if (item.bel != BelId())
belname = ctx->getBelName(item.bel).c_str(ctx);
QString pinname = item.pin.c_str(ctx);
QtProperty *dhItem = addSubGroup(belpinsItem, belname + "-" + pinname);
addProperty(dhItem, QVariant::String, "Bel", belname, ElementType::BEL);
addProperty(dhItem, QVariant::String, "PortPin", pinname);
}
int counter = 0;
QtProperty *pipsDownItem = addSubGroup(topItem, "Pips Downhill");
for (const auto &item : ctx->getPipsDownhill(wire)) {
addProperty(pipsDownItem, QVariant::String, "", ctx->getPipName(item).c_str(ctx), ElementType::PIP);
counter++;
if (counter == 50) {
addProperty(pipsDownItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE);
break;
}
}
counter = 0;
QtProperty *pipsUpItem = addSubGroup(topItem, "Pips Uphill");
for (const auto &item : ctx->getPipsUphill(wire)) {
addProperty(pipsUpItem, QVariant::String, "", ctx->getPipName(item).c_str(ctx), ElementType::PIP);
counter++;
if (counter == 50) {
addProperty(pipsUpItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE);
break;
}
}
} else if (type == ElementType::PIP) {
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
PipId pip = ctx->getPipByName(c);
QtProperty *topItem = addTopLevelProperty("Pip");
addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
addProperty(topItem, QVariant::String, "Type", ctx->getPipType(pip).c_str(ctx));
addProperty(topItem, QVariant::Bool, "Available", ctx->checkPipAvail(pip));
addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundPipNet(pip)), ElementType::NET);
if (ctx->getConflictingPipWire(pip) != WireId()) {
addProperty(topItem, QVariant::String, "Conflicting Wire",
ctx->getWireName(ctx->getConflictingPipWire(pip)).c_str(ctx), ElementType::WIRE);
} else {
addProperty(topItem, QVariant::String, "Conflicting Wire", "", ElementType::NONE);
}
addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingPipNet(pip)),
ElementType::NET);
addProperty(topItem, QVariant::String, "Src Wire", ctx->getWireName(ctx->getPipSrcWire(pip)).c_str(ctx),
ElementType::WIRE);
addProperty(topItem, QVariant::String, "Dest Wire", ctx->getWireName(ctx->getPipDstWire(pip)).c_str(ctx),
ElementType::WIRE);
QtProperty *attrsItem = addSubGroup(topItem, "Attributes");
for (auto &item : ctx->getPipAttrs(pip)) {
addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str());
}
DelayInfo delay = ctx->getPipDelay(pip);
QtProperty *delayItem = addSubGroup(topItem, "Delay");
addProperty(delayItem, QVariant::Double, "Min Raise", delay.minRaiseDelay());
addProperty(delayItem, QVariant::Double, "Max Raise", delay.maxRaiseDelay());
addProperty(delayItem, QVariant::Double, "Min Fall", delay.minFallDelay());
addProperty(delayItem, QVariant::Double, "Max Fall", delay.maxFallDelay());
} else if (type == ElementType::NET) {
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
NetInfo *net = ctx->nets.at(c).get();
QtProperty *topItem = addTopLevelProperty("Net");
addProperty(topItem, QVariant::String, "Name", net->name.c_str(ctx));
QtProperty *driverItem = addSubGroup(topItem, "Driver");
addProperty(driverItem, QVariant::String, "Port", net->driver.port.c_str(ctx));
addProperty(driverItem, QVariant::Double, "Budget", net->driver.budget);
if (net->driver.cell)
addProperty(driverItem, QVariant::String, "Cell", net->driver.cell->name.c_str(ctx), ElementType::CELL);
else
addProperty(driverItem, QVariant::String, "Cell", "", ElementType::CELL);
QtProperty *usersItem = addSubGroup(topItem, "Users");
for (auto &item : net->users) {
QtProperty *portItem = addSubGroup(usersItem, item.port.c_str(ctx));
addProperty(portItem, QVariant::String, "Port", item.port.c_str(ctx));
addProperty(portItem, QVariant::Double, "Budget", item.budget);
if (item.cell)
addProperty(portItem, QVariant::String, "Cell", item.cell->name.c_str(ctx), ElementType::CELL);
else
addProperty(portItem, QVariant::String, "Cell", "", ElementType::CELL);
}
QtProperty *attrsItem = addSubGroup(topItem, "Attributes");
for (auto &item : net->attrs) {
addProperty(attrsItem, QVariant::String, item.first.c_str(ctx),
item.second.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str());
}
QtProperty *wiresItem = addSubGroup(topItem, "Wires");
for (auto &item : net->wires) {
auto name = ctx->getWireName(item.first).c_str(ctx);
QtProperty *wireItem = addSubGroup(wiresItem, name);
addProperty(wireItem, QVariant::String, "Wire", name, ElementType::WIRE);
if (item.second.pip != PipId())
addProperty(wireItem, QVariant::String, "Pip", ctx->getPipName(item.second.pip).c_str(ctx),
ElementType::PIP);
else
addProperty(wireItem, QVariant::String, "Pip", "", ElementType::PIP);
addProperty(wireItem, QVariant::Int, "Strength", (int)item.second.strength);
}
} else if (type == ElementType::CELL) {
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
CellInfo *cell = ctx->cells.at(c).get();
QtProperty *topItem = addTopLevelProperty("Cell");
addProperty(topItem, QVariant::String, "Name", cell->name.c_str(ctx));
addProperty(topItem, QVariant::String, "Type", cell->type.c_str(ctx));
if (cell->bel != BelId())
addProperty(topItem, QVariant::String, "Bel", ctx->getBelName(cell->bel).c_str(ctx), ElementType::BEL);
else
addProperty(topItem, QVariant::String, "Bel", "", ElementType::BEL);
addProperty(topItem, QVariant::Int, "Bel strength", int(cell->belStrength));
QtProperty *cellPortsItem = addSubGroup(topItem, "Ports");
for (auto &item : cell->ports) {
PortInfo p = item.second;
QtProperty *portInfoItem = addSubGroup(cellPortsItem, p.name.c_str(ctx));
addProperty(portInfoItem, QVariant::String, "Name", p.name.c_str(ctx));
addProperty(portInfoItem, QVariant::Int, "Type", int(p.type));
if (p.net)
addProperty(portInfoItem, QVariant::String, "Net", p.net->name.c_str(ctx), ElementType::NET);
else
addProperty(portInfoItem, QVariant::String, "Net", "", ElementType::NET);
}
QtProperty *cellAttrItem = addSubGroup(topItem, "Attributes");
for (auto &item : cell->attrs) {
addProperty(cellAttrItem, QVariant::String, item.first.c_str(ctx),
item.second.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str());
}
QtProperty *cellParamsItem = addSubGroup(topItem, "Parameters");
for (auto &item : cell->params) {
addProperty(cellParamsItem, QVariant::String, item.first.c_str(ctx),
item.second.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str());
}
QtProperty *cellPinsItem = groupManager->addProperty("Pins");
topItem->addSubProperty(cellPinsItem);
for (auto &item : cell->pins) {
std::string cell_port = item.first.c_str(ctx);
std::string bel_pin = item.second.c_str(ctx);
QtProperty *pinGroupItem = addSubGroup(cellPortsItem, (cell_port + " -> " + bel_pin).c_str());
addProperty(pinGroupItem, QVariant::String, "Cell", cell_port.c_str(), ElementType::CELL);
addProperty(pinGroupItem, QVariant::String, "Bel", bel_pin.c_str(), ElementType::BEL);
}
}
}
std::vector<DecalXY> DesignWidget::getDecals(ElementType type, IdString value)
{
std::vector<DecalXY> decals;
switch (type) {
case ElementType::BEL: {
BelId bel = ctx->getBelByName(value);
if (bel != BelId()) {
decals.push_back(ctx->getBelDecal(bel));
}
} break;
case ElementType::WIRE: {
WireId wire = ctx->getWireByName(value);
if (wire != WireId()) {
decals.push_back(ctx->getWireDecal(wire));
}
} break;
case ElementType::PIP: {
PipId pip = ctx->getPipByName(value);
if (pip != PipId()) {
decals.push_back(ctx->getPipDecal(pip));
}
} break;
case ElementType::NET: {
NetInfo *net = ctx->nets.at(value).get();
for (auto &item : net->wires) {
decals.push_back(ctx->getWireDecal(item.first));
if (item.second.pip != PipId()) {
decals.push_back(ctx->getPipDecal(item.second.pip));
}
}
} break;
case ElementType::CELL: {
CellInfo *cell = ctx->cells.at(value).get();
if (cell->bel != BelId()) {
decals.push_back(ctx->getBelDecal(cell->bel));
}
} break;
default:
break;
}
return decals;
}
void DesignWidget::updateHighlightGroup(QList<TreeModel::Item *> items, int group)
{
const bool shouldClear = items.size() == 1;
for (auto item : items) {
if (highlightSelected.contains(item)) {
if (shouldClear && highlightSelected[item] == group) {
highlightSelected.remove(item);
} else
highlightSelected[item] = group;
} else
highlightSelected.insert(item, group);
}
std::vector<DecalXY> decals[8];
for (auto it : highlightSelected.toStdMap()) {
std::vector<DecalXY> d = getDecals(it.first->type(), it.first->id());
std::move(d.begin(), d.end(), std::back_inserter(decals[it.second]));
}
for (int i = 0; i < 8; i++)
Q_EMIT highlight(decals[i], i);
}
void DesignWidget::prepareMenuProperty(const QPoint &pos)
{
QTreeWidget *tree = propertyEditor->treeWidget();
QList<TreeModel::Item *> items;
for (auto itemContextMenu : tree->selectedItems()) {
QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu);
if (!browserItem)
continue;
QtProperty *selectedProperty = browserItem->property();
ElementType type = getElementTypeByName(selectedProperty->propertyId());
if (type == ElementType::NONE)
continue;
IdString value = ctx->id(selectedProperty->valueText().toStdString());
auto node = getTreeByElementType(type)->nodeForId(value);
if (!node)
continue;
items.append(*node);
}
int selectedIndex = -1;
if (items.size() == 1) {
TreeModel::Item *item = items.at(0);
if (highlightSelected.contains(item))
selectedIndex = highlightSelected[item];
}
QMenu menu(this);
QAction *selectAction = new QAction("&Select", this);
connect(selectAction, &QAction::triggered, this, [this, items] {
std::vector<DecalXY> decals;
for (auto clickItem : items) {
std::vector<DecalXY> d = getDecals(clickItem->type(), clickItem->id());
std::move(d.begin(), d.end(), std::back_inserter(decals));
}
Q_EMIT selected(decals, false);
});
menu.addAction(selectAction);
QMenu *subMenu = menu.addMenu("Highlight");
QActionGroup *group = new QActionGroup(this);
group->setExclusive(true);
for (int i = 0; i < 8; i++) {
QPixmap pixmap(32, 32);
pixmap.fill(QColor(highlightColors[i]));
QAction *action = new QAction(QIcon(pixmap), ("Group " + std::to_string(i)).c_str(), this);
action->setCheckable(true);
subMenu->addAction(action);
group->addAction(action);
if (selectedIndex == i)
action->setChecked(true);
connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); });
}
menu.exec(tree->mapToGlobal(pos));
}
void DesignWidget::prepareMenuTree(int num, const QPoint &pos)
{
int selectedIndex = -1;
if (selectionModel[num]->selectedIndexes().size() == 0)
return;
QList<TreeModel::Item *> items;
for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) {
for (auto index : selectionModel[i]->selectedIndexes()) {
TreeModel::Item *item = treeModel[i]->nodeFromIndex(index);
items.append(item);
}
}
if (items.size() == 1) {
TreeModel::Item *item = items.at(0);
if (highlightSelected.contains(item))
selectedIndex = highlightSelected[item];
}
QMenu menu(this);
QMenu *subMenu = menu.addMenu("Highlight");
QActionGroup *group = new QActionGroup(this);
group->setExclusive(true);
for (int i = 0; i < 8; i++) {
QPixmap pixmap(32, 32);
pixmap.fill(QColor(highlightColors[i]));
QAction *action = new QAction(QIcon(pixmap), ("Group " + std::to_string(i)).c_str(), this);
action->setCheckable(true);
subMenu->addAction(action);
group->addAction(action);
if (selectedIndex == i)
action->setChecked(true);
connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); });
}
menu.exec(treeView[num]->mapToGlobal(pos));
}
void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column)
{
QtProperty *selectedProperty = propertyEditor->itemToBrowserItem(item)->property();
ElementType type = getElementTypeByName(selectedProperty->propertyId());
if (type == ElementType::NONE)
return;
auto it = getTreeByElementType(type)->nodeForId(ctx->id(selectedProperty->valueText().toStdString()));
if (it) {
int num = getIndexByElementType(type);
clearAllSelectionModels();
if (tabWidget->currentIndex() != num)
tabWidget->setCurrentIndex(num);
selectionModel[num]->setCurrentIndex(getTreeByElementType(type)->indexFromNode(*it),
QItemSelectionModel::ClearAndSelect);
}
}
void DesignWidget::onDoubleClicked(const QModelIndex &index) { Q_EMIT zoomSelected(); }
void DesignWidget::onSearchInserted()
{
if (currentSearch == searchEdit->text() && currentIndexTab == tabWidget->currentIndex()) {
currentIndex++;
if (currentIndex >= currentSearchIndexes.size())
currentIndex = 0;
} else {
std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex);
std::lock_guard<std::mutex> lock(ctx->mutex);
currentSearch = searchEdit->text();
currentSearchIndexes = treeModel[tabWidget->currentIndex()]->search(searchEdit->text());
currentIndex = 0;
currentIndexTab = tabWidget->currentIndex();
}
if (currentSearchIndexes.size() > 0 && currentIndex < currentSearchIndexes.size())
selectionModel[tabWidget->currentIndex()]->setCurrentIndex(currentSearchIndexes.at(currentIndex),
QItemSelectionModel::ClearAndSelect);
}
void DesignWidget::onHoverIndexChanged(int num, QModelIndex index)
{
if (index.isValid()) {
TreeModel::Item *item = treeModel[num]->nodeFromIndex(index);
if (item->type() != ElementType::NONE) {
std::vector<DecalXY> decals = getDecals(item->type(), item->id());
if (decals.size() > 0)
Q_EMIT hover(decals.at(0));
return;
}
}
Q_EMIT hover(DecalXY());
}
void DesignWidget::onHoverPropertyChanged(QtBrowserItem *item)
{
if (item != nullptr) {
QtProperty *selectedProperty = item->property();
ElementType type = getElementTypeByName(selectedProperty->propertyId());
if (type != ElementType::NONE) {
IdString value = ctx->id(selectedProperty->valueText().toStdString());
if (value != IdString()) {
auto node = getTreeByElementType(type)->nodeForId(value);
if (node) {
std::vector<DecalXY> decals = getDecals((*node)->type(), (*node)->id());
if (decals.size() > 0)
Q_EMIT hover(decals.at(0));
return;
}
}
}
}
Q_EMIT hover(DecalXY());
}
NEXTPNR_NAMESPACE_END