diff --git a/.classpath b/.classpath
index 90ab0ce0c..69d07f8ed 100644
--- a/.classpath
+++ b/.classpath
@@ -56,5 +56,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.github/workflows/test-rapidwright-wrapper.yml b/.github/workflows/test-rapidwright-wrapper.yml
index a9b79aa39..91e97777a 100644
--- a/.github/workflows/test-rapidwright-wrapper.yml
+++ b/.github/workflows/test-rapidwright-wrapper.yml
@@ -13,11 +13,11 @@ jobs:
steps:
- uses: actions/checkout@v2
- - name: Setup JDK 1.11
+ - name: Setup JDK 1.17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
- java-version: '11'
+ java-version: '17'
cache: 'gradle'
- name: Compile
diff --git a/LICENSE.TXT b/LICENSE.TXT
index 738e6771a..e32dd5a06 100755
--- a/LICENSE.TXT
+++ b/LICENSE.TXT
@@ -105,6 +105,8 @@ RapidWright (Apache 2.0)
- Apache Commons IO 2.20.0 (Apache 2.0)
- Zstd-jni 1.5.5-1 (2-clause BSD)
- Zstandard 1.5.5 (3-clause BSD or GPLv2)
+ - Google Guava 33.5.0 (Apache 2.0)
+ - ELK (Eclipse Layout Kernel) 0.10.0 (Eclipse Public License 2.0)
- Google or-tools-java 9.14.6206 (Apache 2.0)
- Java Native Access jna-5.14.0 (Apache 2.0)
@@ -3237,7 +3239,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************* END LICENSE *********************************
-******************* google-guava 28 - Apache 2.0 ********************************
+******************* google-guava 28, Google Guava 33.5.0 - Apache 2.0 ********************************
Copyright (C) 2013 The Guava Authors
@@ -9073,6 +9075,274 @@ library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+********************************* END LICENSE *********************************
+
+********************** ELK (Eclipse Layout Kernel) (Eclipse Public License v 2.0) *****************
+# Eclipse Public License - v 2.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF
+THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+## 1. DEFINITIONS
+
+“Contribution” means:
+
+- a\) in the case of the initial Contributor, the initial content
+ Distributed under this Agreement, and
+- b\) in the case of each subsequent Contributor:
+ - i\) changes to the Program, and
+ - ii\) additions to the Program;
+
+ where such changes and/or additions to the Program originate from
+ and are Distributed by that particular Contributor. A Contribution
+ “originates” from a Contributor if it was added to the Program by
+ such Contributor itself or anyone acting on such Contributor's
+ behalf. Contributions do not include changes or additions to the
+ Program that are not Modified Works.
+
+“Contributor” means any person or entity that Distributes the Program.
+
+“Licensed Patents” mean patent claims licensable by a Contributor which
+are necessarily infringed by the use or sale of its Contribution alone
+or when combined with the Program.
+
+“Program” means the Contributions Distributed in accordance with this
+Agreement.
+
+“Recipient” means anyone who receives the Program under this Agreement
+or any Secondary License (as applicable), including Contributors.
+
+“Derivative Works” shall mean any work, whether in Source Code or other
+form, that is based on (or derived from) the Program and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship.
+
+“Modified Works” shall mean any work in Source Code or other form that
+results from an addition to, deletion from, or modification of the
+contents of the Program, including, for purposes of clarity any new file
+in Source Code form that contains any contents of the Program. Modified
+Works shall not include works that contain only declarations,
+interfaces, types, classes, structures, or files of the Program solely
+in each case in order to link to, bind by name, or subclass the Program
+or Modified Works thereof.
+
+“Distribute” means the acts of a) distributing or b) making available in
+any manner that enables the transfer of a copy.
+
+“Source Code” means the form of a Program preferred for making
+modifications, including but not limited to software source code,
+documentation source, and configuration files.
+
+“Secondary License” means either the GNU General Public License, Version
+2.0, or any later versions of that license, including any exceptions or
+additional permissions as identified by the initial Contributor.
+
+## 2. GRANT OF RIGHTS
+
+- a\) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free copyright
+ license to reproduce, prepare Derivative Works of, publicly display,
+ publicly perform, Distribute and sublicense the Contribution of such
+ Contributor, if any, and such Derivative Works.
+- b\) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free patent license
+ under Licensed Patents to make, use, sell, offer to sell, import and
+ otherwise transfer the Contribution of such Contributor, if any, in
+ Source Code or other form. This patent license shall apply to the
+ combination of the Contribution and the Program if, at the time the
+ Contribution is added by the Contributor, such addition of the
+ Contribution causes such combination to be covered by the
+ Licensed Patents. The patent license shall not apply to any other
+ combinations which include the Contribution. No hardware per se is
+ licensed hereunder.
+- c\) Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity. Each
+ Contributor disclaims any liability to Recipient for claims brought by
+ any other entity based on infringement of intellectual property rights
+ or otherwise. As a condition to exercising the rights and licenses
+ granted hereunder, each Recipient hereby assumes sole responsibility to
+ secure any other intellectual property rights needed, if any. For
+ example, if a third party patent license is required to allow Recipient
+ to Distribute the Program, it is Recipient's responsibility to acquire
+ that license before distributing the Program.
+- d\) Each Contributor represents that to its knowledge it has sufficient
+ copyright rights in its Contribution, if any, to grant the copyright
+ license set forth in this Agreement.
+- e\) Notwithstanding the terms of any Secondary License, no Contributor
+ makes additional grants to any Recipient (other than those set forth in
+ this Agreement) as a result of such Recipient's receipt of the Program
+ under the terms of a Secondary License (if permitted under the terms of
+ Section 3).
+
+## 3. REQUIREMENTS
+
+3.1 If a Contributor Distributes the Program in any form, then:
+
+- a\) the Program must also be made available as Source Code, in accordance
+ with section 3.2, and the Contributor must accompany the Program with a
+ statement that the Source Code for the Program is available under this
+ Agreement, and informs Recipients how to obtain it in a reasonable
+ manner on or through a medium customarily used for software exchange;
+ and
+- b\) the Contributor may Distribute the Program under a license different
+ than this Agreement, provided that such license:
+ - i\) effectively disclaims on behalf of all other Contributors all
+ warranties and conditions, express and implied, including warranties or
+ conditions of title and non-infringement, and implied warranties or
+ conditions of merchantability and fitness for a particular purpose;
+ - ii\) effectively excludes on behalf of all other Contributors all
+ liability for damages, including direct, indirect, special, incidental
+ and consequential damages, such as lost profits;
+ - iii\) does not attempt to limit or alter the recipients' rights in the
+ Source Code under section 3.2; and
+ - iv\) requires any subsequent distribution of the Program by any party to
+ be under a license that satisfies the requirements of this section 3.
+
+3.2 When the Program is Distributed as Source Code:
+
+- a\) it must be made available under this Agreement, or if the Program (i)
+ is combined with other material in a separate file or files made
+ available under a Secondary License, and (ii) the initial Contributor
+ attached to the Source Code the notice described in Exhibit A of this
+ Agreement, then the Program may be made available under the terms of
+ such Secondary Licenses, and
+- b\) a copy of this Agreement must be included with each copy of
+ the Program.
+
+3.3 Contributors may not remove or alter any copyright, patent,
+trademark, attribution notices, disclaimers of warranty, or limitations
+of liability (‘notices’) contained within the Program from any copy of
+the Program which they Distribute, provided that Contributors may add
+their own appropriate notices.
+
+## 4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities
+with respect to end users, business partners and the like. While this
+license is intended to facilitate the commercial use of the Program, the
+Contributor who includes the Program in a commercial product offering
+should do so in a manner which does not create potential liability for
+other Contributors. Therefore, if a Contributor includes the Program in
+a commercial product offering, such Contributor (“Commercial
+Contributor”) hereby agrees to defend and indemnify every other
+Contributor (“Indemnified Contributor”) against any losses, damages and
+costs (collectively “Losses”) arising from claims, lawsuits and other
+legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such
+Commercial Contributor in connection with its distribution of the
+Program in a commercial product offering. The obligations in this
+section do not apply to any claims or Losses relating to any actual or
+alleged intellectual property infringement. In order to qualify, an
+Indemnified Contributor must: a) promptly notify the Commercial
+Contributor in writing of such claim, and b) allow the Commercial
+Contributor to control, and cooperate with the Commercial Contributor
+in, the defense and any related settlement negotiations. The Indemnified
+Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those
+performance claims and warranties, and if a court requires any other
+Contributor to pay any damages as a result, the Commercial Contributor
+must pay those damages.
+
+## 5. NO WARRANTY {#warranty}
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS”
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
+TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+PURPOSE. Each Recipient is solely responsible for determining the
+appropriateness of using and distributing the Program and assumes all
+risks associated with its exercise of rights under this Agreement,
+including but not limited to the risks and costs of program errors,
+compliance with applicable laws, damage to or loss of data, programs or
+equipment, and unavailability or interruption of operations.
+
+## 6. DISCLAIMER OF LIABILITY {#disclaimer}
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
+SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+## 7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further action
+by the parties hereto, such provision shall be reformed to the minimum
+extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including
+a cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails
+to comply with any of the material terms or conditions of this Agreement
+and does not cure such failure in a reasonable period of time after
+becoming aware of such noncompliance. If all Recipient's rights under
+this Agreement terminate, Recipient agrees to cease use and distribution
+of the Program as soon as reasonably practicable. However, Recipient's
+obligations under this Agreement and any licenses granted by Recipient
+relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement,
+but in order to avoid inconsistency the Agreement is copyrighted and may
+only be modified in the following manner. The Agreement Steward reserves
+the right to publish new versions (including revisions) of this
+Agreement from time to time. No one other than the Agreement Steward has
+the right to modify this Agreement. The Eclipse Foundation is the
+initial Agreement Steward. The Eclipse Foundation may assign the
+responsibility to serve as the Agreement Steward to a suitable separate
+entity. Each new version of the Agreement will be given a distinguishing
+version number. The Program (including Contributions) may always be
+Distributed subject to the version of the Agreement under which it was
+received. In addition, after a new version of the Agreement is
+published, Contributor may elect to Distribute the Program (including
+its Contributions) under the new version.
+
+Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
+receives no rights or licenses to the intellectual property of any
+Contributor under this Agreement, whether expressly, by implication,
+estoppel or otherwise. All rights in the Program not expressly granted
+under this Agreement are reserved. Nothing in this Agreement is intended
+to be enforceable by any entity that is not a Contributor or Recipient.
+No third-party beneficiary rights are created under this Agreement.
+
+## Exhibit A – Form of Secondary Licenses Notice {#exhibit-a}
+
+“This Source Code may also be made available under the following
+Secondary Licenses when the conditions for such availability set forth
+in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
+version(s), and exceptions or additional permissions here}.”
+
+> Simply including a copy of this Agreement, including this Exhibit A is
+> not sufficient to license the Source Code under Secondary Licenses.
+>
+> If it is not possible or desirable to put the notice in a particular
+> file, then You may include the notice in a location (such as a LICENSE
+> file in a relevant directory) where a recipient would be likely to
+> look for such a notice.
+>
+> You may add additional accurate notices of copyright ownership.
+
********************************* END LICENSE *********************************
********************** Jacl 1.4.1 (multiple licenses) ****************
@@ -9284,4 +9554,4 @@ CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
ENHANCEMENTS, OR MODIFICATIONS.
-********************************* END LICENSE *********************************
\ No newline at end of file
+********************************* END LICENSE *********************************
diff --git a/common.gradle b/common.gradle
index 3531f40c3..90fa8094f 100644
--- a/common.gradle
+++ b/common.gradle
@@ -48,6 +48,14 @@ dependencies {
api 'commons-io:commons-io:2.20.0'
api 'com.xilinx.rapidwright:qtjambi-'+os+':4.5.2_01'
api 'com.xilinx.rapidwright:jupyter-kernel-jsr223:1.0.1'
+ if (JavaVersion.current() < JavaVersion.VERSION_17) {
+ // Eclipse ELK doesn't compile to support JDK8 anymore
+ api 'com.xilinx.rapidwright:org.eclipse.elk-deps-jdk8:0.10.0-rc1'
+ } else {
+ api 'org.eclipse.elk:org.eclipse.elk.core:0.10.0'
+ api 'org.eclipse.elk:org.eclipse.elk.graph:0.10.0'
+ api 'org.eclipse.elk:org.eclipse.elk.alg.layered:0.10.0'
+ }
api 'com.xilinx.rapidwright:jacl:1.4.1'
testFixturesApi 'org.junit.jupiter:junit-jupiter-api:5.7.1'
testFixturesApi 'org.junit.jupiter:junit-jupiter-engine:5.7.1'
diff --git a/src/com/xilinx/rapidwright/gui/HierCellInstTreeWidgetItem.java b/src/com/xilinx/rapidwright/gui/HierCellInstTreeWidgetItem.java
index a0b53b830..7d76391d5 100644
--- a/src/com/xilinx/rapidwright/gui/HierCellInstTreeWidgetItem.java
+++ b/src/com/xilinx/rapidwright/gui/HierCellInstTreeWidgetItem.java
@@ -36,6 +36,10 @@ public HierCellInstTreeWidgetItem(QTreeWidgetItem parent) {
super(parent);
}
+ public HierCellInstTreeWidgetItem(NetlistTreeWidget netlistTreeWidget) {
+ super(netlistTreeWidget);
+ }
+
public void setInst(EDIFHierCellInst inst) {
this.inst = inst;
}
diff --git a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java
index 044c98bdc..d7d13db46 100644
--- a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java
+++ b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java
@@ -22,20 +22,30 @@
*/
package com.xilinx.rapidwright.gui;
+import com.trolltech.qt.core.QModelIndex;
import com.trolltech.qt.core.Qt.DockWidgetArea;
import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QDockWidget;
import com.trolltech.qt.gui.QDockWidget.DockWidgetFeature;
import com.trolltech.qt.gui.QMainWindow;
import com.trolltech.qt.gui.QTreeWidget;
+import com.trolltech.qt.gui.QTreeWidgetItem;
import com.trolltech.qt.gui.QWidget;
import com.xilinx.rapidwright.design.Design;
+import com.xilinx.rapidwright.edif.EDIFCell;
+import com.xilinx.rapidwright.edif.EDIFCellInst;
+import com.xilinx.rapidwright.edif.EDIFHierCellInst;
+import com.xilinx.rapidwright.edif.EDIFNet;
import com.xilinx.rapidwright.edif.EDIFNetlist;
+import com.xilinx.rapidwright.edif.EDIFPort;
import com.xilinx.rapidwright.edif.EDIFTools;
public class NetlistBrowser extends QMainWindow {
- private QTreeWidget treeWidget;
+ private NetlistTreeWidget treeWidget;
+ private QDockWidget schematicWidget;
+ private SchematicScene schematicScene;
+ private SchematicView schematicView;
private Design design;
@@ -90,12 +100,44 @@ private void init() {
resize(1280, 1024);
treeWidget = new NetlistTreeWidget("Netlist", netlist);
- // treeWidget.doubleClicked.connect(this,"showPart(QModelIndex)");
+ treeWidget.clicked.connect(this, "selectNetlistItem(QModelIndex)");
QDockWidget dockWidget = new QDockWidget(tr("Design"), this);
dockWidget.setWidget(treeWidget);
dockWidget.setFeatures(DockWidgetFeature.DockWidgetMovable);
addDockWidget(DockWidgetArea.LeftDockWidgetArea, dockWidget);
+
+ // Add schematic viewer on the right
+ schematicScene = new SchematicScene(netlist);
+ schematicView = new SchematicView(schematicScene);
+ schematicWidget = new QDockWidget(tr("Schematic"), this);
+ schematicWidget.setWidget(schematicView);
+ schematicWidget.setFeatures(DockWidgetFeature.DockWidgetMovable);
+ addDockWidget(DockWidgetArea.RightDockWidgetArea, schematicWidget);
+
+ schematicScene.objectSelected.connect(this, "selectFromSchematic(String)");
+ schematicScene.cellDrawn.connect(schematicView, "zoomToFit()");
+ }
+
+ public void selectNetlistItem(QModelIndex index) {
+ QTreeWidgetItem item = treeWidget.getItemFromIndex(index);
+ if (item instanceof HierCellInstTreeWidgetItem) {
+ EDIFHierCellInst cellInst = ((HierCellInstTreeWidgetItem) item).getInst();
+ schematicScene.drawCell(cellInst, true);
+ } else if (item.data(0, 0) instanceof EDIFPort) {
+ EDIFPort port = (EDIFPort) item.data(0, 0);
+ schematicScene.selectObject(item.data(1, 0).toString(), true);
+ } else if (item.data(0, 0) instanceof EDIFNet) {
+ schematicScene.selectObject(item.data(1, 0).toString(), true);
+ }
+ }
+
+ public void selectFromSchematic(String lookup) {
+ QTreeWidgetItem item = treeWidget.getItemByStringLookup(lookup);
+ if (item != null) {
+ treeWidget.setCurrentItem(item);
+ treeWidget.scrollToItem(item);
+ }
}
/**
diff --git a/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java b/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java
index a10204ae9..8995db00a 100644
--- a/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java
+++ b/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java
@@ -24,7 +24,9 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import com.trolltech.qt.core.QModelIndex;
import com.trolltech.qt.gui.QTreeWidget;
@@ -32,6 +34,8 @@
import com.xilinx.rapidwright.edif.EDIFCell;
import com.xilinx.rapidwright.edif.EDIFCellInst;
import com.xilinx.rapidwright.edif.EDIFHierCellInst;
+import com.xilinx.rapidwright.edif.EDIFHierNet;
+import com.xilinx.rapidwright.edif.EDIFHierPortInst;
import com.xilinx.rapidwright.edif.EDIFNet;
import com.xilinx.rapidwright.edif.EDIFNetlist;
import com.xilinx.rapidwright.edif.EDIFPort;
@@ -45,13 +49,16 @@ public class NetlistTreeWidget extends QTreeWidget {
private QTreeWidgetItem rootItem;
+ private Map objectLookup = new HashMap<>();
+
private static final String DUMMY = "_*DUMMY*_";
public NetlistTreeWidget(String header, EDIFNetlist netlist) {
this.netlist = netlist;
setColumnCount(1);
setHeaderLabel(header);
- QTreeWidgetItem root = new QTreeWidgetItem(this);
+ HierCellInstTreeWidgetItem root = new HierCellInstTreeWidgetItem(this);
+ root.setInst(netlist.getTopHierCellInst());
QTreeWidgetItem dummy = new QTreeWidgetItem(root);
dummy.setText(0, DUMMY);
rootItem = populateCellInst(root, netlist.getTopHierCellInst());
@@ -68,14 +75,18 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i
EDIFCell cell = inst.getCellType();
curr.setText(0, inst.getInst().getName() + " (" + cell.getName() + ")");
+ boolean isTop = inst.isTopLevelInst();
QTreeWidgetItem ports = new QTreeWidgetItem(curr);
ports.setText(0, "Ports (" + cell.getPorts().size() + ")");
- List edifPorts = new ArrayList<>(cell.getPorts());
- Collections.sort(edifPorts);
- for (EDIFPort port : edifPorts) {
+
+ for (EDIFHierPortInst portInst : inst.getHierPortInsts()) {
QTreeWidgetItem n = new QTreeWidgetItem(ports);
- n.setText(0, port.getName() + " (" + port.getDirection() + ")");
+ n.setData(0, 0, portInst);
+ String portLookup = "PORT:" + (isTop ? portInst.getPortInst().getName() : portInst.toString());
+ n.setData(1, 0, portLookup);
+ n.setText(0, portInst.getPortInst().getName() + " (" + portInst.getPortInst().getDirection() + ")");
+ objectLookup.put(portLookup, n);
}
ports.setExpanded(false);
@@ -86,16 +97,21 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i
Collections.sort(edifNets);
for (EDIFNet net : edifNets) {
QTreeWidgetItem n = new QTreeWidgetItem(nets);
+ EDIFHierNet hierNet = inst.getNet(net.getName());
+ n.setData(0, 0, hierNet);
+ String netLookup = "NET:" + hierNet.toString();
+ n.setData(1, 0, netLookup);
n.setText(0, net.getName());
+ objectLookup.put(netLookup, n);
}
nets.setExpanded(false);
- List leaves = new ArrayList<>();
+ List leaves = new ArrayList<>();
List nonLeaves = new ArrayList<>();
for (EDIFCellInst child : cell.getCellInsts()) {
if (child.getCellType().isLeafCellOrBlackBox()) {
- leaves.add(child);
+ leaves.add(inst.getChild(child));
} else {
nonLeaves.add(inst.getChild(child));
}
@@ -105,9 +121,13 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i
QTreeWidgetItem leafCells = new QTreeWidgetItem(curr);
leafCells.setText(0, "Leaf Cells (" + leaves.size() + ")");
- for (EDIFCellInst i : leaves) {
+ for (EDIFHierCellInst i : leaves) {
QTreeWidgetItem leaf = new QTreeWidgetItem(leafCells);
- leaf.setText(0, i.getName() + " (" + i.getCellName() + ")");
+ leaf.setText(0, i.getInst().getName() + " (" + i.getCellName() + ")");
+ leaf.setData(0, 0, i);
+ String leafLookup = "INST:" + i.toString();
+ leaf.setData(1, 0, leafLookup);
+ objectLookup.put(leafLookup, leaf);
}
leafCells.setExpanded(false);
@@ -116,6 +136,9 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i
HierCellInstTreeWidgetItem cellInst = new HierCellInstTreeWidgetItem(curr);
cellInst.setText(0, i.getInst().getName() + " (" + i.getCellName() + ")");
cellInst.setInst(i);
+ String instLookup = "INST:" + i.toString();
+ cellInst.setData(1, 0, instLookup);
+ objectLookup.put(instLookup, cellInst);
QTreeWidgetItem dummy = new QTreeWidgetItem(cellInst);
dummy.setText(0, DUMMY);
}
@@ -144,4 +167,12 @@ public EDIFNetlist getNetlist() {
public QTreeWidgetItem getRootItem() {
return rootItem;
}
+
+ public QTreeWidgetItem getItemFromIndex(QModelIndex index) {
+ return this.itemFromIndex(index);
+ }
+
+ public QTreeWidgetItem getItemByStringLookup(String lookup) {
+ return objectLookup.get(lookup);
+ }
}
diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java
new file mode 100644
index 000000000..83a0b5969
--- /dev/null
+++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java
@@ -0,0 +1,767 @@
+/*
+ *
+ * Copyright (c) 2025, Advanced Micro Devices, Inc.
+ * All rights reserved.
+ *
+ * Author: Chris Lavin, AMD Advanced Research and Development.
+ *
+ * This file is part of RapidWright.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.xilinx.rapidwright.gui;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.eclipse.elk.core.IGraphLayoutEngine;
+import org.eclipse.elk.core.RecursiveGraphLayoutEngine;
+import org.eclipse.elk.core.math.ElkPadding;
+import org.eclipse.elk.core.options.CoreOptions;
+import org.eclipse.elk.core.options.NodeLabelPlacement;
+import org.eclipse.elk.core.options.PortConstraints;
+import org.eclipse.elk.core.options.PortSide;
+import org.eclipse.elk.core.util.BasicProgressMonitor;
+import org.eclipse.elk.core.util.IElkProgressMonitor;
+import org.eclipse.elk.graph.ElkBendPoint;
+import org.eclipse.elk.graph.ElkEdge;
+import org.eclipse.elk.graph.ElkEdgeSection;
+import org.eclipse.elk.graph.ElkGraphElement;
+import org.eclipse.elk.graph.ElkGraphFactory;
+import org.eclipse.elk.graph.ElkLabel;
+import org.eclipse.elk.graph.ElkNode;
+import org.eclipse.elk.graph.ElkPort;
+
+import com.trolltech.qt.core.QPointF;
+import com.trolltech.qt.core.QRectF;
+import com.trolltech.qt.core.QSizeF;
+import com.trolltech.qt.core.Qt.KeyboardModifier;
+import com.trolltech.qt.gui.QAbstractGraphicsShapeItem;
+import com.trolltech.qt.gui.QBrush;
+import com.trolltech.qt.gui.QColor;
+import com.trolltech.qt.gui.QFont;
+import com.trolltech.qt.gui.QFontMetrics;
+import com.trolltech.qt.gui.QGraphicsItemInterface;
+import com.trolltech.qt.gui.QGraphicsLineItem;
+import com.trolltech.qt.gui.QGraphicsPathItem;
+import com.trolltech.qt.gui.QGraphicsPolygonItem;
+import com.trolltech.qt.gui.QGraphicsRectItem;
+import com.trolltech.qt.gui.QGraphicsScene;
+import com.trolltech.qt.gui.QGraphicsSceneMouseEvent;
+import com.trolltech.qt.gui.QGraphicsSimpleTextItem;
+import com.trolltech.qt.gui.QPainterPath;
+import com.trolltech.qt.gui.QPen;
+import com.trolltech.qt.gui.QPolygonF;
+import com.xilinx.rapidwright.edif.EDIFCell;
+import com.xilinx.rapidwright.edif.EDIFCellInst;
+import com.xilinx.rapidwright.edif.EDIFHierCellInst;
+import com.xilinx.rapidwright.edif.EDIFHierPortInst;
+import com.xilinx.rapidwright.edif.EDIFNet;
+import com.xilinx.rapidwright.edif.EDIFNetlist;
+import com.xilinx.rapidwright.edif.EDIFPort;
+import com.xilinx.rapidwright.edif.EDIFPortInst;
+
+public class SchematicScene extends QGraphicsScene {
+
+ private EDIFNetlist netlist;
+
+ private EDIFHierCellInst currCellInst;
+
+ private ElkNode elkRoot;
+
+ private Map portInstMap = new HashMap<>();
+ private Map elkNodeTopPortMap = new HashMap<>();
+ private Map elkNodeCellMap = new HashMap<>();
+ private Map> lookupMap = new HashMap<>();
+ private Set expandedCellInsts = new HashSet<>();
+ private Set selectedObjects = new HashSet<>();
+
+ public Signal1 objectSelected = new Signal1<>();
+ public Signal0 cellDrawn = new Signal0();
+
+ private static QFont FONT = new QFont("Arial", 8);
+ private static QFont BUTTON_TEXT_FONT = new QFont("Arial", 10, QFont.Weight.Bold.value());
+
+ private static final QBrush BLACK_BRUSH = new QBrush(QColor.black);
+ private static final QPen BLACK_PEN = new QPen(QColor.black);
+ private static final QPen CLICK_PEN = new QPen(new QColor(0, 0, 0, 0), 10);
+ private static final QBrush CLICK_BRUSH = new QBrush(new QColor(0, 0, 0, 0));
+
+ private static QFontMetrics fm = new QFontMetrics(FONT);
+ private static QBrush canvasBackgroundBrush = new QBrush(QColor.white);
+ private static final QBrush PORT_BRUSH = new QBrush(QColor.white);
+ private static final QPen PORT_PEN = BLACK_PEN;
+ private static final QPen NET_PEN = new QPen(new QColor(91, 203, 75));
+ private static final QPen NET_CLICK_PEN = CLICK_PEN;
+ private static final QPen SELECTED_PEN = new QPen(new QColor(0, 0, 255), 3);
+
+ private static final QBrush CELL_BRUSH = new QBrush(new QColor(255, 255, 210));
+ private static final QPen CELL_PEN = new QPen(QColor.black);
+ private static final QBrush HIER_CELL_BRUSH = new QBrush(new QColor(173, 216, 230));
+ private static final QPen HIER_CELL_PEN = new QPen(new QColor(100, 149, 237));
+ private static final QBrush EXPANDED_HIER_CELL_BRUSH = new QBrush(new QColor(255, 255, 255, 0));
+ private static final QPen EXPANDED_HIER_CELL_PEN = new QPen(new QColor(100, 149, 237), 2);
+
+ private static final QBrush BUTTON_BRUSH = new QBrush(new QColor(72, 61, 139));
+ private static final QPen BUTTON_PEN = new QPen(new QColor(72, 61, 139));
+ private static final QBrush BUTTON_TEXT_BRUSH = new QBrush(QColor.white);
+
+ private static final double PORT_SIZE = 6.0;
+ private static final double MIN_NODE_HEIGHT = 30.0;
+ private static final double MIN_NODE_WIDTH = 40.0;
+
+ private static final double PORT_HEIGHT = 20.0;
+ private static final double PORT_NAME_BUFFER = 40.0;
+ private static final double TOP_PORT_WIDTH = 20.0;
+ private static final double TOP_PORT_HEIGHT = 14.0;
+ private static final double PORT_LABEL_SPACING = 4.0;
+
+ private static final double NODE_TO_NODE_SPACING = 40.0;
+ private static final double EDGE_TO_NODE_SPACING = 20.0;
+ private static final double SIDE_PADDING = 10.0;
+
+
+ private static final double POINT_DIST = TOP_PORT_HEIGHT * 0.2; // Pointy part of the port
+
+ private static final double BUTTON_SIZE = 16.0;
+ private static final double BUTTON_RADIUS = 3.0;
+ private static final double LABEL_BUFFER = 2.0;
+ private static final double PIN_LINE_LENGTH = 10.0;
+
+ private static final String HIER_BUTTON = "HIER_BUTTON";
+ private static final String CLICK = "CLICK";
+
+ public SchematicScene(EDIFNetlist netlist) {
+ super();
+ this.netlist = netlist;
+ setBackgroundBrush(canvasBackgroundBrush);
+ }
+
+ private ElkNode createElkRoot(EDIFHierCellInst cellInst) {
+ ElkNode root = ElkGraphFactory.eINSTANCE.createElkNode();
+ root.setIdentifier(cellInst.toString());
+ applyElkNodeProperties(root);
+ return root;
+ }
+
+ private void applyElkNodeProperties(ElkNode root) {
+ root.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
+ root.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_TO_NODE_SPACING);
+ root.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_TO_NODE_SPACING);
+ }
+
+ public void drawCell(EDIFHierCellInst cellInst, boolean zoomFit) {
+ clear();
+ portInstMap.clear();
+ elkNodeTopPortMap.clear();
+ elkNodeCellMap.clear();
+ lookupMap.clear();
+ selectedObjects.clear();
+ this.currCellInst = cellInst;
+ elkRoot = createElkRoot(cellInst);
+ populateCellContent(cellInst, elkRoot, "");
+ elkNodeCellMap.put(elkRoot, cellInst);
+
+ IGraphLayoutEngine engine = new RecursiveGraphLayoutEngine();
+ IElkProgressMonitor monitor = new BasicProgressMonitor();
+ engine.layout(elkRoot, monitor);
+
+ renderSchematic();
+ if (zoomFit) {
+ cellDrawn.emit();
+ }
+ }
+
+ public void renderSchematic() {
+ double extraWidthBuffer = elkRoot.getWidth() * 0.25 + 100;
+ double extraHeightBuffer = elkRoot.getHeight() * 0.25 + 100;
+ QSizeF size = new QSizeF(elkRoot.getWidth() + extraWidthBuffer, elkRoot.getHeight() + extraHeightBuffer);
+
+ setSceneRect(new QRectF(new QPointF(0, 0), size));
+
+ // We create arrow-shaped top port ElkNodes to serve as targets for top ports
+ for (ElkNode topPort : elkRoot.getChildren()) {
+ EDIFHierPortInst hierPortInst = elkNodeTopPortMap.get(topPort);
+ if (hierPortInst != null) {
+ QPolygonF portShape = createPortShape(topPort, hierPortInst.isOutput());
+ QGraphicsPolygonItem port = addPolygon(portShape, PORT_PEN, PORT_BRUSH);
+ String lookup = "PORT:" + hierPortInst.toString();
+ port.setData(0, lookup);
+ String portInstName = hierPortInst.getPortInst().getName();
+ port.setToolTip(portInstName + (hierPortInst.isOutput() ? "(Output)" : "(Input)"));
+ port.setAcceptsHoverEvents(true);
+ lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(port);
+
+ QGraphicsSimpleTextItem portLabel = addSimpleText(portInstName);
+ portLabel.setBrush(BLACK_BRUSH);
+ portLabel.setFont(FONT);
+ double portShapeX = topPort.getX() + (topPort.getWidth() - TOP_PORT_WIDTH) / 2.0;
+ double portShapeY = topPort.getY() + (topPort.getHeight() - TOP_PORT_HEIGHT) / 2.0;
+ double labelX = portShapeX;
+ if (hierPortInst.isOutput()) {
+ // Position label to the right of top ports
+ labelX += TOP_PORT_WIDTH + PORT_LABEL_SPACING;
+ } else {
+ // To the left for inputs
+ labelX -= portLabel.boundingRect().width() + PORT_LABEL_SPACING;
+ }
+ double labelY = portShapeY + (TOP_PORT_HEIGHT - portLabel.boundingRect().height()) / 2.0;
+ portLabel.setPos(labelX, labelY);
+ portLabel.setZValue(4);
+ }
+ }
+
+ renderNode(elkRoot, 0, 0, "");
+ renderEdges(elkRoot, 0, 0);
+ }
+
+ private void renderNode(ElkNode parent, double xOffset, double yOffset, String prefix) {
+ EDIFCell cell = elkNodeCellMap.get(parent).getCellType();
+ for (ElkNode child : parent.getChildren()) {
+ if (elkNodeTopPortMap.containsKey(child)) {
+ // Rendered in renderSchematic()
+ continue;
+ }
+ String relCellInstName = child.getIdentifier();
+ if (relCellInstName.startsWith(parent.getIdentifier()) && parent.getIdentifier().length() > 0) {
+ // Remove hierarchical reference
+ relCellInstName = relCellInstName.substring(parent.getIdentifier().length() + 1);
+ }
+ EDIFCellInst eci = cell.getCellInst(relCellInstName);
+
+ boolean isLeaf = true;
+ String cellInstName = prefix + relCellInstName;
+ boolean isExpanded = expandedCellInsts.contains(cellInstName);
+ if (child.getChildren().size() > 0) {
+ isLeaf = false;
+ } else if (eci != null && !eci.getCellType().isLeafCellOrBlackBox()) {
+ isLeaf = false;
+ }
+
+ double x = xOffset + child.getX();
+ double y = yOffset + child.getY();
+
+ QGraphicsRectItem rect = null;
+ QGraphicsRectItem clickRect = addRect(x, y, child.getWidth(), child.getHeight(), CLICK_PEN, CLICK_BRUSH);
+ ;
+ if (isLeaf) {
+ rect = addRect(x, y, child.getWidth(), child.getHeight(), CELL_PEN, CELL_BRUSH);
+ clickRect.setZValue(6);
+ } else {
+ if (isExpanded) {
+ rect = addRect(x, y, child.getWidth(), child.getHeight(), EXPANDED_HIER_CELL_PEN, EXPANDED_HIER_CELL_BRUSH);
+ clickRect.setZValue(4);
+ } else {
+ rect = addRect(x, y, child.getWidth(), child.getHeight(), HIER_CELL_PEN, HIER_CELL_BRUSH);
+ clickRect.setZValue(6);
+ }
+ createHierButton(child, isExpanded, cellInstName, xOffset, yOffset);
+ }
+ String instLookup = "INST:" + child.getIdentifier();
+ clickRect.setData(0, instLookup);
+ lookupMap.computeIfAbsent(instLookup, l -> new ArrayList<>()).add(rect);
+
+ ElkLabel instNameLabel = child.getLabels().get(0); // instance name
+ ElkLabel cellTypeLabel = child.getLabels().get(1); // cell type
+
+ // Instance name centered above cell rectangle
+ QGraphicsSimpleTextItem instLabel = addSimpleText(instNameLabel.getText());
+ instLabel.setBrush(BLACK_BRUSH);
+ instLabel.setFont(FONT);
+ double instLabelX = x + (child.getWidth() - instLabel.boundingRect().width()) / 2.0;
+ double instLabelY = y - instLabel.boundingRect().height();
+ instLabel.setPos(instLabelX, instLabelY - LABEL_BUFFER);
+ instLabel.setZValue(5);
+
+ // Cell type centered below cell rectangle
+ QGraphicsSimpleTextItem cellLabel = addSimpleText(cellTypeLabel.getText());
+ cellLabel.setBrush(BLACK_BRUSH);
+ cellLabel.setFont(FONT);
+ double cellLabelX = x + (child.getWidth() - cellLabel.boundingRect().width()) / 2.0;
+ double cellLabelY = y + child.getHeight();
+ cellLabel.setPos(cellLabelX, cellLabelY + LABEL_BUFFER);
+ cellLabel.setZValue(5);
+
+ for (ElkPort port : child.getPorts()) {
+ double yPort = y + port.getY() + port.getHeight() / 2.0;
+ PortSide side = port.getProperty(CoreOptions.PORT_SIDE);
+ drawPin(child, port, yPort, side, isExpanded, xOffset, child.getIdentifier());
+
+ QGraphicsSimpleTextItem pinLabel = addSimpleText(port.getIdentifier());
+ pinLabel.setBrush(BLACK_BRUSH);
+ pinLabel.setFont(FONT);
+ pinLabel.setZValue(5);
+ double textWidth = pinLabel.boundingRect().width();
+ double textHeight = pinLabel.boundingRect().height();
+ double labelX = x;
+ double labelY = y;
+
+ if (!isLeaf) {
+ labelX += side == PortSide.EAST ? child.getWidth() + LABEL_BUFFER : - textWidth - LABEL_BUFFER;
+ labelY = yPort - textHeight + LABEL_BUFFER;
+ } else {
+ labelX += side == PortSide.EAST ? child.getWidth() - textWidth - 2*LABEL_BUFFER : 2*LABEL_BUFFER;
+ labelY = yPort - textHeight / 2.0;
+ }
+ pinLabel.setPos(labelX, labelY);
+ }
+
+ if (child.getChildren().size() > 0) {
+ renderNode(child, x, y, prefix + relCellInstName + "/");
+ }
+ }
+ }
+
+ private void drawPin(ElkNode cell, ElkPort port, double y, PortSide side, boolean isExpanded, double xOffset, String parentInst) {
+ double x1 = cell.getX() + xOffset + (side == PortSide.EAST ? cell.getWidth() : -PIN_LINE_LENGTH);
+ double x2 = cell.getX() + xOffset + (side == PortSide.EAST ? cell.getWidth() + PIN_LINE_LENGTH : 0);
+
+ // Draw outer pins
+ QGraphicsLineItem pinLine = addLine(x1, y, x2, y, BLACK_PEN);
+ pinLine.setZValue(2);
+ String lookup = "PORT:" + parentInst + "/" + port.getIdentifier();
+ pinLine.setData(0, lookup);
+ lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(pinLine);
+ // Add a thick invisible area to make them easier to click on
+ QGraphicsLineItem clickLine = addLine(x1, y, x2, y, CLICK_PEN);
+ clickLine.setZValue(3);
+ clickLine.setData(0, lookup);
+
+ if (isExpanded) {
+ // Draw inner pins
+ x1 = cell.getX() + xOffset + (side == PortSide.EAST ? cell.getWidth() - PIN_LINE_LENGTH : 0);
+ x2 = cell.getX() + xOffset + (side == PortSide.EAST ? cell.getWidth() : PIN_LINE_LENGTH);
+ QGraphicsLineItem innerPinLine = addLine(x1, y, x2, y, BLACK_PEN);
+ innerPinLine.setZValue(2);
+ // Add a thick invisible area to make them easier to click on
+ QGraphicsLineItem innerClickLine = addLine(x1, y, x2, y, CLICK_PEN);
+ innerClickLine.setZValue(2);
+ }
+ }
+
+ private QGraphicsPathItem createHierButton(ElkNode node, boolean isExpanded, String expandedCellName, double xOffset, double yOffset) {
+ double buttonX = xOffset + node.getX() + BUTTON_SIZE / 2;
+ double buttonY = yOffset + node.getY() + BUTTON_SIZE / 2;
+ QPainterPath path = new QPainterPath();
+ path.addRoundedRect(buttonX, buttonY, BUTTON_SIZE, BUTTON_SIZE, BUTTON_RADIUS, BUTTON_RADIUS);
+
+ // Create a rounded rectangle for the hierarchy button
+ QGraphicsPathItem button = addPath(path, BUTTON_PEN, BUTTON_BRUSH);
+ String data = HIER_BUTTON + ": " + expandedCellName + " : " + (isExpanded ? "COLLAPSE" : "EXPAND");
+ button.setData(0, data);
+ String tooltip = isExpanded ? "Collapse" : "Expand";
+ button.setToolTip(tooltip);
+ button.setZValue(10);
+
+ // Add text to the button
+ QGraphicsSimpleTextItem buttonText = addSimpleText(isExpanded ? "-" : "+");
+ buttonText.setBrush(BUTTON_TEXT_BRUSH);
+ buttonText.setFont(BUTTON_TEXT_FONT);
+ double textX = buttonX + (BUTTON_SIZE - buttonText.boundingRect().width()) / 2.0;
+ double textY = buttonY + (BUTTON_SIZE - buttonText.boundingRect().height()) / 2.0;
+ buttonText.setPos(textX, textY);
+ buttonText.setZValue(11);
+ buttonText.setData(0, data);
+ buttonText.setToolTip(tooltip);
+
+ return button;
+ }
+
+ private void renderEdges(ElkNode parent, double xOffset, double yOffset) {
+ for (ElkEdge e : parent.getContainedEdges()) {
+ if (e.getSections().isEmpty()) continue;
+ ElkEdgeSection s = e.getSections().get(0);
+
+ double startX = xOffset + s.getStartX();
+ double startY = yOffset + s.getStartY();
+ double endX = xOffset + s.getEndX();
+ double endY = yOffset + s.getEndY();
+
+ if (!e.getSources().isEmpty()) {
+ ElkPort srcPort = (ElkPort) e.getSources().get(0);
+ ElkNode portParent = (ElkNode) srcPort.getParent();
+ EDIFHierPortInst portInst = elkNodeTopPortMap.get(portParent);
+ if (portInst != null && portInst.getPortInst().isTopLevelPort()) {
+ QPointF topPortLoc = getTopPortConnectionPoint(portParent, portInst.isOutput());
+ startX = topPortLoc.x();
+ startY = topPortLoc.y();
+ }
+ }
+
+ if (!e.getTargets().isEmpty()) {
+ ElkPort snkPort = (ElkPort) e.getTargets().get(0);
+ ElkNode portParent = (ElkNode) snkPort.getParent();
+ EDIFHierPortInst portInst = elkNodeTopPortMap.get(portParent);
+ if (portInst != null && portInst.getPortInst().isTopLevelPort()) {
+ QPointF topPortLoc = getTopPortConnectionPoint(portParent, portInst.isOutput());
+ endX = topPortLoc.x();
+ endY = topPortLoc.y();
+ }
+ }
+
+ double lastX = startX;
+ double lastY = startY;
+ String id = e.getIdentifier();
+ String lookup = "NET:" + (id == null ? "" : id);
+ for (ElkBendPoint bp : s.getBendPoints()) {
+ double bpX = bp.getX() + xOffset;
+ double bpY = bp.getY() + yOffset;
+ drawSegment(lastX, lastY, bpX, bpY, lookup);
+ lastX = bpX;
+ lastY = bpY;
+ }
+
+ // Draw final segment
+ drawSegment(lastX, lastY, endX, endY, lookup);
+ }
+
+ for (ElkNode child : parent.getChildren()) {
+ if (child.getChildren().size() > 0) {
+ renderEdges(child, child.getX() + xOffset, child.getY() + yOffset);
+ }
+ }
+ }
+
+ private void drawSegment(double lastX, double lastY, double endX, double endY, String lookup) {
+ QGraphicsLineItem line = addLine(lastX, lastY, endX, endY, NET_PEN);
+ line.setData(0, lookup);
+ line.setZValue(0);
+ lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(line);
+ QGraphicsLineItem clickLine = addLine(lastX, lastY, endX, endY, NET_CLICK_PEN);
+ clickLine.setData(0, lookup);
+ clickLine.setData(1, CLICK);
+ clickLine.setZValue(5);
+ lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(clickLine);
+ }
+
+ private QPointF getTopPortConnectionPoint(ElkNode port, boolean isOutput) {
+ double portX = port.getX() + (port.getWidth() - TOP_PORT_WIDTH) / 2.0;
+ double portY = port.getY() + (port.getHeight() - TOP_PORT_HEIGHT) / 2.0;
+ return new QPointF(portX + (isOutput ? -POINT_DIST : POINT_DIST) + TOP_PORT_WIDTH, portY + TOP_PORT_HEIGHT / 2);
+ }
+
+ private static QPolygonF createPortShape(ElkNode topPort, boolean isOutput) {
+ double portX = topPort.getX();
+ double portY = topPort.getY();
+ double height = topPort.getHeight();
+ double width = topPort.getWidth();
+ double x = portX + (width - TOP_PORT_WIDTH) / 2.0;
+ double y = portY + (height - TOP_PORT_HEIGHT) / 2.0;
+
+ QPolygonF portShape = new QPolygonF();
+
+ if (isOutput) {
+ // Point to the left
+ portShape.add(new QPointF(x + POINT_DIST, y));
+ portShape.add(new QPointF(x + TOP_PORT_WIDTH, y));
+ portShape.add(new QPointF(x + TOP_PORT_WIDTH, y + TOP_PORT_HEIGHT));
+ portShape.add(new QPointF(x + POINT_DIST, y + TOP_PORT_HEIGHT));
+ portShape.add(new QPointF(x - POINT_DIST, y + (TOP_PORT_HEIGHT / 2)));
+ } else {
+ // Input points to the right
+ portShape.add(new QPointF(x, y));
+ portShape.add(new QPointF(x + TOP_PORT_WIDTH - POINT_DIST, y));
+ portShape.add(new QPointF(x + TOP_PORT_WIDTH + POINT_DIST, y + (TOP_PORT_HEIGHT / 2)));
+ portShape.add(new QPointF(x + TOP_PORT_WIDTH - POINT_DIST, y + TOP_PORT_HEIGHT));
+ portShape.add(new QPointF(x, y + TOP_PORT_HEIGHT));
+ }
+
+ return portShape;
+ }
+
+ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, String prefix) {
+ ElkGraphFactory f = ElkGraphFactory.eINSTANCE;
+ EDIFCell cell = cellInst.getCellType();
+
+ // Only create top level port shapes if this is the top cell
+ if (prefix.isEmpty()) {
+ for (EDIFPort topPort : cell.getPorts()) {
+ for (int i : (topPort.isBus() ? topPort.getBitBlastedIndicies() : new int[] { 0 })) {
+ String portInstName = topPort.getPortInstNameFromPort(i);
+ ElkNode elkTopPortNode = f.createElkNode();
+ EDIFHierPortInst hierPortInst = cellInst.getPortInst(portInstName);
+ elkNodeTopPortMap.put(elkTopPortNode, hierPortInst);
+ elkTopPortNode.setDimensions(TOP_PORT_WIDTH + POINT_DIST, TOP_PORT_HEIGHT);
+ elkTopPortNode.setIdentifier(portInstName);
+ elkTopPortNode.setParent(parent);
+ parent.getChildren().add(elkTopPortNode);
+ labelElkNode(elkTopPortNode, portInstName);
+ elkTopPortNode.setProperty(CoreOptions.NODE_LABELS_PLACEMENT,
+ EnumSet.of(topPort.isOutput() ? NodeLabelPlacement.H_RIGHT : NodeLabelPlacement.H_LEFT));
+ ElkPort elkTopPort = f.createElkPort();
+ elkTopPort.setParent(elkTopPortNode);
+ elkTopPort.setIdentifier(portInstName);
+ elkTopPort.setProperty(CoreOptions.PORT_SIDE, topPort.isOutput() ? PortSide.EAST : PortSide.WEST);
+ elkTopPort.setProperty(CoreOptions.PORT_INDEX, 1);
+ elkTopPort.setDimensions(PORT_SIZE, PORT_SIZE);
+ portInstMap.put(cellInst.getPortInst(portInstName), elkTopPort);
+ elkTopPortNode.getPorts().add(elkTopPort);
+ }
+ }
+ }
+
+ Map instNodeMap = new HashMap<>();
+ for (EDIFCellInst inst : cell.getCellInsts()) {
+ ElkNode elkInst = f.createElkNode();
+ elkInst.setParent(parent);
+ parent.getChildren().add(elkInst);
+ EDIFHierCellInst childInst = cellInst.getChild(inst);
+ elkInst.setIdentifier(childInst.toString());
+ elkInst.setProperty(CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_ORDER);
+ instNodeMap.put(childInst, elkInst);
+ elkNodeCellMap.put(elkInst, childInst);
+
+ boolean isHierCell = !inst.getCellType().isLeafCellOrBlackBox();
+
+ // Create labels
+ elkInst.setProperty(CoreOptions.NODE_LABELS_PLACEMENT, NodeLabelPlacement.outsideTopCenter());
+ labelElkNode(elkInst, inst.getName());
+ labelElkNode(elkInst, inst.getCellName());
+
+ // Create ports
+ Map westPorts = new TreeMap<>();
+ Map eastPorts = new TreeMap<>();
+ double longestWestName = 0;
+ double longestEastName = 0;
+ // for (EDIFPort port : inst.getCellPorts()) {
+ for (EDIFHierPortInst hierPortInst : childInst.getHierPortInsts()) {
+ String name = hierPortInst.getPortInst().getName();
+ if (hierPortInst.isInput()) {
+ westPorts.put(name, hierPortInst);
+ longestWestName = Math.max(longestWestName, fm.width(name));
+ } else {
+ eastPorts.put(name, hierPortInst);
+ longestEastName = Math.max(longestEastName, fm.width(name));
+ }
+ }
+
+ int portStartIndex = 0;
+ portStartIndex = createElkPorts(portStartIndex, isHierCell, elkInst, westPorts, PortSide.WEST);
+ portStartIndex = createElkPorts(portStartIndex, isHierCell, elkInst, eastPorts, PortSide.EAST);
+
+ // Calculate cell rectangle size
+ int maxPins = Math.max(westPorts.size(), eastPorts.size());
+ double height = Math.max(MIN_NODE_HEIGHT, PORT_HEIGHT * maxPins);
+ double width = Math.max(MIN_NODE_WIDTH, longestWestName + longestEastName + PORT_NAME_BUFFER);
+ elkInst.setDimensions(width, height);
+
+ if (isHierCell && expandedCellInsts.contains(prefix + inst.getName())) {
+ applyElkNodeProperties(elkInst);
+ // Extra spacing for button placement
+ elkInst.setProperty(CoreOptions.PADDING, new ElkPadding(
+ BUTTON_SIZE * 2, // Top
+ SIDE_PADDING, // Side
+ fm.height() + LABEL_BUFFER * 2, // Bottom
+ SIDE_PADDING // Side
+ ));
+ createExpandedCellInnerPorts(childInst);
+ populateCellContent(childInst, elkInst, prefix + inst.getName() + "/");
+ }
+ }
+
+ for (EDIFNet net : cell.getNets()) {
+ List drivers = new ArrayList<>();
+ List sinks = new ArrayList<>();
+
+ for (EDIFPortInst p : net.getPortInsts()) {
+ EDIFHierPortInst hierPortInst = new EDIFHierPortInst(cellInst, p);
+ if (p.isTopLevelPort()) {
+ if (p.isOutput()) {
+ sinks.add(hierPortInst);
+ } else {
+ drivers.add(hierPortInst);
+ }
+ } else {
+ if (p.isOutput()) {
+ drivers.add(hierPortInst);
+ } else {
+ sinks.add(hierPortInst);
+ }
+ }
+ }
+
+ for (EDIFHierPortInst d : drivers) {
+ ElkPort driver = getOrCreateElkPort(d, prefix, instNodeMap, cellInst);
+ for (EDIFHierPortInst s : sinks) {
+ ElkPort sink = getOrCreateElkPort(s, prefix, instNodeMap, cellInst);
+ if (driver == null || sink == null)
+ continue;
+
+ ElkEdge edge = ElkGraphFactory.eINSTANCE.createElkEdge();
+ edge.setContainingNode(parent);
+ edge.setIdentifier(cellInst.toString() + "/" + net.getName());
+ edge.getSources().add(driver);
+ edge.getTargets().add(sink);
+ parent.getContainedEdges().add(edge);
+ }
+ }
+ }
+ }
+
+ private void createExpandedCellInnerPorts(EDIFHierCellInst inst) {
+ for (EDIFPort port : inst.getCellType().getPorts()) {
+ for (int i : (port.isBus() ? port.getBitBlastedIndicies() : new int[] { 0 })) {
+ EDIFPortInst outerPortInst = inst.getInst().getPortInst(port.getPortInstNameFromPort(i));
+ ElkPort outerElkPort = portInstMap.get(outerPortInst);
+ // Map the inner port inst to the outer one so nets are aligned
+ if (outerElkPort != null) {
+ EDIFPortInst innerPortInst = port.getInternalPortInstFromIndex(i);
+ if (innerPortInst != null) {
+ portInstMap.put(inst.getPortInst(innerPortInst.getName()), outerElkPort);
+ }
+ }
+ }
+ }
+ }
+
+ private ElkPort getOrCreateElkPort(EDIFHierPortInst p, String prefix, Map instNodeMap,
+ EDIFHierCellInst cellInst) {
+ ElkPort port = portInstMap.get(p);
+ if (port == null) {
+ port = ElkGraphFactory.eINSTANCE.createElkPort();
+ if (p.getPortInst().isTopLevelPort()) {
+ p = cellInst.getPortInst(p.getPortInst().getName());
+ port = portInstMap.get(p);
+ } else {
+ ElkNode inst = instNodeMap.get(p.getFullHierarchicalInst());
+ port.setParent(inst);
+ inst.getPorts().add(port);
+ port.setIdentifier(p.getPortInst().getName());
+ port.setDimensions(PORT_SIZE, PORT_SIZE);
+ port.setProperty(CoreOptions.PORT_SIDE, p.isOutput() ? PortSide.EAST : PortSide.WEST);
+ }
+
+ portInstMap.put(p, port);
+ }
+ return port;
+ }
+
+ private int createElkPorts(int startIdx, boolean isHierCell, ElkNode parent, Map portNames,
+ PortSide side) {
+ for (Entry e : portNames.entrySet()) {
+ ElkPort port = ElkGraphFactory.eINSTANCE.createElkPort();
+ portInstMap.put(e.getValue(), port);
+ port.setParent(parent);
+ parent.getPorts().add(port);
+ port.setIdentifier(e.getKey());
+ port.setProperty(CoreOptions.PORT_SIDE, side);
+ port.setProperty(CoreOptions.PORT_INDEX, startIdx++);
+ port.setDimensions(PORT_SIZE, PORT_SIZE);
+
+ if (isHierCell) {
+ labelElkNode(port, e.getKey());
+ }
+
+ }
+ return startIdx;
+ }
+
+ private void labelElkNode(ElkGraphElement n, String name) {
+ ElkLabel label = ElkGraphFactory.eINSTANCE.createElkLabel();
+ label.setText(name);
+ label.setParent(n);
+ n.getLabels().add(label);
+ label.setDimensions(fm.width(label.getText()), fm.height());
+ }
+
+ public void mousePressEvent(QGraphicsSceneMouseEvent event) {
+ QGraphicsItemInterface item = itemAt(event.scenePos());
+ if (item != null && item.data(0) != null) {
+ String data = item.data(0).toString();
+ if (data.startsWith(HIER_BUTTON)) {
+ String[] parts = data.split(":");
+ toggleCellInstExpansion(parts[1].trim());
+ } else if (data.startsWith("INST:") || data.startsWith("NET:") || data.startsWith("PORT:")) {
+ boolean ctrlPressed = event.modifiers().isSet(KeyboardModifier.ControlModifier);
+ toggleSelection(data, ctrlPressed);
+ }
+ }
+ super.mousePressEvent(event);
+ }
+
+ private void toggleCellInstExpansion(String cellInstName) {
+ if (expandedCellInsts.contains(cellInstName)) {
+ expandedCellInsts.remove(cellInstName);
+ } else {
+ expandedCellInsts.add(cellInstName);
+ }
+ boolean zoomFit = false;
+ drawCell(currCellInst, zoomFit);
+ }
+
+ private void toggleSelection(String lookup, boolean multipleSelection) {
+ if (!multipleSelection) {
+ clearSelections();
+ }
+
+ if (selectedObjects.contains(lookup)) {
+ updateSelectionHighlight(lookup, false);
+ selectedObjects.remove(lookup);
+ } else {
+ updateSelectionHighlight(lookup, true);
+ selectedObjects.add(lookup);
+ }
+
+ objectSelected.emit(lookup);
+ }
+
+ private void clearSelections() {
+ for (String unselected : selectedObjects) {
+ updateSelectionHighlight(unselected, false);
+ }
+ selectedObjects.clear();
+ }
+
+ public void selectObject(String lookup, boolean clearPreviousSelections) {
+ if (clearPreviousSelections) {
+ clearSelection();
+ }
+ selectedObjects.remove(lookup);
+ toggleSelection(lookup, !clearPreviousSelections);
+ }
+
+ private void updateSelectionHighlight(String lookup, boolean isSelected) {
+ List