From b750873cad83c678b5c34da64b4bc8fed8bed2a2 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 7 Oct 2025 12:54:41 -0600 Subject: [PATCH 01/32] Initial Schematic viewer with several issues. Signed-off-by: Chris Lavin --- .classpath | 16 + common.gradle | 3 + .../rapidwright/gui/NetlistBrowser.java | 26 +- .../rapidwright/gui/NetlistTreeWidget.java | 13 + .../rapidwright/gui/SchematicScene.java | 574 ++++++++++++++++++ .../xilinx/rapidwright/gui/SchematicView.java | 162 +++++ 6 files changed, 792 insertions(+), 2 deletions(-) create mode 100644 src/com/xilinx/rapidwright/gui/SchematicScene.java create mode 100644 src/com/xilinx/rapidwright/gui/SchematicView.java diff --git a/.classpath b/.classpath index feb64e603..7f06ca8f5 100644 --- a/.classpath +++ b/.classpath @@ -55,5 +55,21 @@ + + + + + + + + + + + + + + + + diff --git a/common.gradle b/common.gradle index 97c0300d1..079d06233 100644 --- a/common.gradle +++ b/common.gradle @@ -47,6 +47,9 @@ dependencies { api 'commons-io:commons-io:2.11.0' api 'com.xilinx.rapidwright:qtjambi-'+os+':4.5.2_01' api 'com.xilinx.rapidwright:jupyter-kernel-jsr223:1.0.1' + 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' testFixturesApi 'org.junit.jupiter:junit-jupiter-api:5.7.1' testFixturesApi 'org.junit.jupiter:junit-jupiter-engine:5.7.1' testFixturesApi 'org.junit.jupiter:junit-jupiter-params:5.7.1' diff --git a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java index 044c98bdc..dbeb4ec86 100644 --- a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java +++ b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java @@ -22,20 +22,26 @@ */ 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.EDIFNetlist; 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 +96,28 @@ 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); + } + + public void selectNetlistItem(QModelIndex index) { + QTreeWidgetItem item = treeWidget.getItemFromIndex(index); + if (item instanceof HierCellInstTreeWidgetItem) { + EDIFCell cell = ((HierCellInstTreeWidgetItem) item).getInst().getCellType(); + schematicScene.drawCell(cell); + } } /** diff --git a/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java b/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java index a10204ae9..5bfd07ecc 100644 --- a/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java +++ b/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import com.trolltech.qt.core.QModelIndex; @@ -45,6 +46,8 @@ public class NetlistTreeWidget extends QTreeWidget { private QTreeWidgetItem rootItem; + private HashMap instLookup = new HashMap<>(); + private static final String DUMMY = "_*DUMMY*_"; public NetlistTreeWidget(String header, EDIFNetlist netlist) { @@ -116,6 +119,7 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i HierCellInstTreeWidgetItem cellInst = new HierCellInstTreeWidgetItem(curr); cellInst.setText(0, i.getInst().getName() + " (" + i.getCellName() + ")"); cellInst.setInst(i); + instLookup.put(i.toString(), cellInst); QTreeWidgetItem dummy = new QTreeWidgetItem(cellInst); dummy.setText(0, DUMMY); } @@ -144,4 +148,13 @@ public EDIFNetlist getNetlist() { public QTreeWidgetItem getRootItem() { return rootItem; } + + public QTreeWidgetItem getItemFromIndex(QModelIndex index) { + return this.itemFromIndex(index); + } + + public HierCellInstTreeWidgetItem getItemByHierInstName(String name) { + return instLookup.get(name); + } + } diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java new file mode 100644 index 000000000..a1a41a904 --- /dev/null +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -0,0 +1,574 @@ +/* + * + * 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.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.elk.core.IGraphLayoutEngine; +import org.eclipse.elk.core.RecursiveGraphLayoutEngine; +import org.eclipse.elk.core.options.CoreOptions; +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.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.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.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.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 EDIFCell currCell; + + private ElkNode elkRoot; + + private Map portInstMap = new HashMap<>(); + private Map elkNodeTopPortMap = new HashMap<>(); + private Map elkNodeCellMap = new HashMap<>(); + private Map> lookupMap = new HashMap<>(); + + 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 QFontMetrics fm = new QFontMetrics(FONT); + private static QBrush canvasBackgroundBrush = new QBrush(QColor.white); + private static final QBrush PORT_BRUSH = BLACK_BRUSH; + 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 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 = 20.0; + private static final double MIN_NODE_WIDTH = 40.0; + + private static final double PORT_HEIGHT = 34.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_MARGIN = 20.0; + private static final double PORT_LABEL_SPACING = 4.0; + + 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; + + + public SchematicScene(EDIFNetlist netlist) { + super(); + this.netlist = netlist; + setBackgroundBrush(canvasBackgroundBrush); + } + + private ElkNode createElkRoot(EDIFCell cell) { + ElkNode root = ElkGraphFactory.eINSTANCE.createElkNode(); + root.setIdentifier(cell.getName()); + root.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered"); + + return root; + } + + public void drawCell(EDIFCell cell) { + clear(); + this.currCell = cell; + elkRoot = createElkRoot(cell); + populateCellContent(cell, elkRoot, ""); + elkNodeCellMap.put(elkRoot, cell); + + IGraphLayoutEngine engine = new RecursiveGraphLayoutEngine(); + IElkProgressMonitor monitor = new BasicProgressMonitor(); + engine.layout(elkRoot, monitor); + + renderSchematic(); + } + + 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)); + + // Top ports are treated as ElkNodes + for (ElkNode topPort : elkRoot.getChildren()) { + EDIFPortInst portInst = elkNodeTopPortMap.get(topPort); + if (portInst != null) { + QPolygonF portShape = createPortShape(topPort, portInst.isOutput()); + QGraphicsPolygonItem port = addPolygon(portShape, PORT_PEN, PORT_BRUSH); + String lookup = "PORT:" + portInst.getName(); + port.setData(0, lookup); + port.setToolTip(portInst.getName() + (portInst.isOutput() ? "(Output)" : "(Input)")); + port.setAcceptsHoverEvents(true); + lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(port); + + QGraphicsSimpleTextItem portLabel = addSimpleText(portInst.getName()); + portLabel.setBrush(PORT_BRUSH); + portLabel.setFont(FONT); + double portShapeX = topPort.getX() + (topPort.getWidth() - TOP_PORT_WIDTH) / 2.0; + double portShapeY = topPort.getY() + (topPort.getHeight() - TOP_PORT_HEIGHT - PORT_MARGIN) / 2.0; + double labelX = portShapeX + (TOP_PORT_WIDTH - portLabel.boundingRect().width()) / 2.0; + double labelY = portShapeY + TOP_PORT_HEIGHT + PORT_LABEL_SPACING; + portLabel.setPos(labelX, labelY); + portLabel.setZValue(4); + } + } + + renderNode(elkRoot); + renderEdges(elkRoot); + } + + private void renderNode(ElkNode parent) { + EDIFCell cell = elkNodeCellMap.get(parent); + for (ElkNode child : parent.getChildren()) { + if (elkNodeTopPortMap.containsKey(child)) { + // Rendered in renderSchematic() + continue; + } + EDIFCellInst eci = cell.getCellInst(child.getIdentifier()); + + boolean isLeaf = true; + boolean isExpanded = false; + if (child.getChildren().size() > 0) { + isLeaf = false; + } else if (eci != null && !eci.getCellType().isLeafCellOrBlackBox()) { + isLeaf = false; + } + + if (isLeaf) { + addRect(child.getX(), child.getY(), child.getWidth(), child.getHeight(), CELL_PEN, CELL_BRUSH); + } else { + String expandedCellName = !elkRoot.equals(parent) ? child.getIdentifier() : eci.getName(); + if (isExpanded) { + // TODO + addRect(child.getX(), child.getY(), child.getWidth(), child.getHeight(), + EXPANDED_HIER_CELL_PEN, EXPANDED_HIER_CELL_BRUSH); + } else { + addRect(child.getX(), child.getY(), child.getWidth(), child.getHeight(), + HIER_CELL_PEN, HIER_CELL_BRUSH); + } + createHierButton(child, isExpanded, expandedCellName); + } + + + 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 = child.getX() + (child.getWidth() - instLabel.boundingRect().width()) / 2.0; + double instLabelY = child.getY() - 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 = child.getX() + (child.getWidth() - cellLabel.boundingRect().width()) / 2.0; + double cellLabelY = child.getY() + child.getHeight(); + cellLabel.setPos(cellLabelX, cellLabelY + LABEL_BUFFER); + cellLabel.setZValue(5); + + for (ElkPort port : child.getPorts()) { + double y = child.getY() + port.getY() + port.getHeight() / 2.0; + PortSide side = port.getProperty(CoreOptions.PORT_SIDE); + drawPin(child, port, y, side, isExpanded); + + QGraphicsSimpleTextItem pinLabel = addSimpleText(port.getIdentifier()); + pinLabel.setBrush(BLACK_BRUSH); + pinLabel.setFont(FONT); + pinLabel.setZValue(6); + double textWidth = pinLabel.boundingRect().width(); + double textHeight = pinLabel.boundingRect().height(); + double labelX = child.getX(); + double labelY = child.getY(); + + if (!isLeaf) { + labelX += side == PortSide.EAST ? child.getWidth() + LABEL_BUFFER : - textWidth - LABEL_BUFFER; + labelY += y - textHeight - 2*LABEL_BUFFER; + } else { + labelX += side == PortSide.EAST ? child.getWidth() - textWidth - 2*LABEL_BUFFER : 2*LABEL_BUFFER; + labelY = y - textHeight / 2.0; + } + pinLabel.setPos(labelX, labelY); + } + + if (child.getChildren().size() > 0) { + renderNode(child); + } + } + } + + private void drawPin(ElkNode cell, ElkPort port, double y, PortSide side, boolean isExpanded) { + double x1 = cell.getX() + (side == PortSide.EAST ? cell.getWidth() : -PIN_LINE_LENGTH); + double x2 = cell.getX() + (side == PortSide.EAST ? cell.getWidth() + PIN_LINE_LENGTH : 0); + + // Draw outer pins + QGraphicsLineItem pinLine = addLine(x1, y, x2, y, BLACK_PEN); + pinLine.setZValue(2); + // Add a thick invisible area to make them easier to click on + QGraphicsLineItem clickLine = addLine(x1, y, x2, y, CLICK_PEN); + clickLine.setZValue(2); + + if (isExpanded) { + // Draw inner pins + x1 = cell.getX() + (side == PortSide.EAST ? cell.getWidth() - PIN_LINE_LENGTH : 0); + x2 = cell.getX() + (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 buttonX = node.getX() + (node.getWidth() - BUTTON_SIZE) / 2.0; + double buttonY = node.getY() + (node.getHeight() - BUTTON_SIZE) / 2.0; + 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); + button.setData(0, "HIER_BUTTON: " + expandedCellName + " : " + (isExpanded ? "COLLAPSE" : "EXPAND")); + button.setToolTip(isExpanded ? "Collapse" : "Expand"); + 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); + + return button; + } + + private void renderEdges(ElkNode parent) { + for (ElkEdge e : parent.getContainedEdges()) { + if (e.getSections().isEmpty()) continue; + ElkEdgeSection s = e.getSections().get(0); + + double startX = s.getStartX(); + double startY = s.getStartY(); + double endX = s.getEndX(); + double endY = s.getEndY(); + + if (!e.getSources().isEmpty()) { + ElkPort srcPort = (ElkPort) s; + ElkNode portParent = (ElkNode) srcPort.getParent(); + EDIFPortInst portInst = elkNodeTopPortMap.get(portParent); + if (portInst != null && portInst.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(); + EDIFPortInst portInst = elkNodeTopPortMap.get(portParent); + if (portInst != null && portInst.isTopLevelPort()) { + QPointF topPortLoc = getTopPortConnectionPoint(portParent, portInst.isOutput()); + endX = topPortLoc.x(); + endY = topPortLoc.y(); + } + } + + double lastX = startX; + double lastY = startY; + for (ElkBendPoint bp : s.getBendPoints()) { + String id = e.getIdentifier(); + String lookup = "NET:" + (id == null ? "" : id); + QGraphicsLineItem line = addLine(lastX, lastY, bp.getX(), bp.getY(), NET_PEN); + line.setData(0, lookup); + line.setZValue(0); + lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(line); + QGraphicsLineItem clickLine = addLine(lastX, lastY, bp.getX(), bp.getY(), NET_CLICK_PEN); + clickLine.setData(0, lookup); + clickLine.setData(1, "CLICK"); + lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(clickLine); + lastX = bp.getX(); + lastY = bp.getY(); + } + } + + for (ElkNode child : parent.getChildren()) { + if (child.getChildren().size() > 0) { + renderEdges(child); + } + } + } + + 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 - PORT_MARGIN) / 2.0; + return new QPointF(portX + (isOutput ? 0 : 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 - PORT_MARGIN) / 2.0; + + QPolygonF portShape = new QPolygonF(); + double pointDist = TOP_PORT_HEIGHT * 0.3; // Pointy part of the port + + if (isOutput) { + // Point to the left + portShape.add(new QPointF(x + pointDist, 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 + pointDist, y + TOP_PORT_HEIGHT)); + portShape.add(new QPointF(x + pointDist, 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 - pointDist, y)); + portShape.add(new QPointF(x + TOP_PORT_WIDTH, y + (TOP_PORT_HEIGHT / 2))); + portShape.add(new QPointF(x + TOP_PORT_WIDTH - pointDist, y + TOP_PORT_HEIGHT)); + portShape.add(new QPointF(x, y + (TOP_PORT_HEIGHT / 2))); + } + + return portShape; + } + + private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { + ElkGraphFactory f = ElkGraphFactory.eINSTANCE; + + for (EDIFPort topPort : cell.getPorts()) { + for (int i : (topPort.isBus() ? topPort.getBitBlastedIndicies() : new int[] { 0 })) { + String portInstName = topPort.getPortInstNameFromPort(0); + ElkNode elkTopPort = f.createElkNode(); + EDIFPortInst portInst = topPort.getInternalPortInstFromIndex(i); + elkNodeTopPortMap.put(elkTopPort, portInst); + elkTopPort.setDimensions(TOP_PORT_WIDTH, TOP_PORT_WIDTH); + elkTopPort.setIdentifier(portInstName); + elkTopPort.setParent(parent); + parent.getChildren().add(elkTopPort); + } + } + + Map instNodeMap = new HashMap<>(); + for (EDIFCellInst inst : cell.getCellInsts()) { + ElkNode elkInst = f.createElkNode(); + elkInst.setParent(parent); + parent.getChildren().add(elkInst); + elkInst.setIdentifier(prefix + inst.getName()); + elkInst.setProperty(CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_ORDER); + instNodeMap.put(inst, elkInst); + elkNodeCellMap.put(elkInst, inst.getCellType()); + + boolean isHierCell = !inst.getCellType().isLeafCellOrBlackBox(); + + // Create labels + labelElkNode(elkInst, inst.getName()); + labelElkNode(elkInst, inst.getCellName()); + + // Create ports + List westPorts = new ArrayList<>(); + List eastPorts = new ArrayList<>(); + double longestWestName = 0; + double longestEastName = 0; + for (EDIFPort port : inst.getCellPorts()) { + if (port.isBus()) { + for (int i : port.getBitBlastedIndicies()) { + String name = port.getPortInstNameFromPort(i); + if (port.isInput()) { + westPorts.add(name); + longestWestName = Math.max(longestWestName, fm.width(name)); + } else { + eastPorts.add(name); + longestEastName = Math.max(longestEastName, fm.width(name)); + } + } + } else { + String name = port.getPortInstNameFromPort(0); + if (port.isInput()) { + westPorts.add(name); + longestWestName = Math.max(longestWestName, fm.width(name)); + } else { + eastPorts.add(name); + longestEastName = Math.max(longestEastName, fm.width(name)); + } + } + } + Collections.sort(westPorts); + Collections.sort(eastPorts); + + 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); + } + + for (EDIFNet net : cell.getNets()) { + List drivers = new ArrayList<>(); + List sinks = new ArrayList<>(); + + for (EDIFPortInst p : net.getPortInsts()) { + if (p.isTopLevelPort()) { + if (p.isOutput()) { + sinks.add(p); + } else { + drivers.add(p); + } + } else { + if (p.isOutput()) { + drivers.add(p); + } else { + sinks.add(p); + } + } + } + + for (EDIFPortInst d : drivers) { + ElkPort driver = getOrCreateElkPort(d, prefix, instNodeMap); + for (EDIFPortInst s : sinks) { + ElkPort sink = getOrCreateElkPort(s, prefix, instNodeMap); + if (driver == null || sink == null) + continue; + if (driver.getParent() == parent && sink.getParent() == parent) { + ElkEdge edge = ElkGraphFactory.eINSTANCE.createElkEdge(); + edge.setContainingNode(parent); + edge.setIdentifier(prefix + net.getName()); + edge.getSources().add(driver); + edge.getTargets().add(sink); + parent.getContainedEdges().add(edge); + } + } + } + } + } + + private ElkPort getOrCreateElkPort(EDIFPortInst p, String prefix, Map instNodeMap) { + ElkPort port = portInstMap.get(p); + if (port == null) { + port = ElkGraphFactory.eINSTANCE.createElkPort(); + if (p.isTopLevelPort()) { + return null; + } else { + ElkNode inst = instNodeMap.get(p.getCellInst()); + port.setParent(inst); + inst.getPorts().add(port); + port.setIdentifier(p.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, List portNames, + PortSide side) { + for (String name : portNames) { + ElkPort port = ElkGraphFactory.eINSTANCE.createElkPort(); + port.setParent(parent); + parent.getPorts().add(port); + port.setIdentifier(name); + port.setProperty(CoreOptions.PORT_SIDE, side); + port.setProperty(CoreOptions.PORT_INDEX, startIdx++); + port.setDimensions(PORT_SIZE, PORT_SIZE); + + if (isHierCell) { + labelElkNode(port, name); + } + + } + 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()); + } +} diff --git a/src/com/xilinx/rapidwright/gui/SchematicView.java b/src/com/xilinx/rapidwright/gui/SchematicView.java new file mode 100644 index 000000000..75238ba28 --- /dev/null +++ b/src/com/xilinx/rapidwright/gui/SchematicView.java @@ -0,0 +1,162 @@ +/* + * + * 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 com.trolltech.qt.core.QPoint; +import com.trolltech.qt.core.QPointF; +import com.trolltech.qt.core.Qt; +import com.trolltech.qt.core.Qt.CursorShape; +import com.trolltech.qt.core.Qt.Key; +import com.trolltech.qt.gui.QCursor; +import com.trolltech.qt.gui.QGraphicsScene; +import com.trolltech.qt.gui.QGraphicsView; +import com.trolltech.qt.gui.QKeyEvent; +import com.trolltech.qt.gui.QMouseEvent; +import com.trolltech.qt.gui.QWheelEvent; + +public class SchematicView extends QGraphicsView { + + private boolean rightPressed; + private QPoint lastPan; + + /** The maximum value to which we can zoom out */ + protected static double zoomMin = 0.05; + /** The maximum value to which we can zoom in */ + protected static double zoomMax = 30; + /** The rate at which we zoom */ + protected static double scaleFactor = 1.15; + + public SchematicView(QGraphicsScene scene) { + super(scene); + } + + /** + * This method is called when any mouse button is pressed. In this case, a right + * click will allow the user to pan the array of tiles. + */ + public void mousePressEvent(QMouseEvent event) { + if (event.button().equals(Qt.MouseButton.RightButton)) { + // For panning the view + rightPressed = true; + lastPan = event.pos(); + setCursor(new QCursor(CursorShape.ClosedHandCursor)); + } + super.mousePressEvent(event); + } + + /** + * This method is called when any mouse button is released. In this case, this + * will disallow the user to pan. + */ + public void mouseReleaseEvent(QMouseEvent event) { + if (event.button().equals(Qt.MouseButton.RightButton)) { + rightPressed = false; + setCursor(new QCursor(CursorShape.ArrowCursor)); + } + super.mouseReleaseEvent(event); + } + + /** + * This method is called when the mouse moves in the window. This will reset the + * window based on the mouse panning. + */ + public void mouseMoveEvent(QMouseEvent event) { + if (rightPressed) { + if (lastPan != null && !lastPan.isNull()) { + // Get how much we panned + QPointF s1 = mapToScene(new QPoint((int) lastPan.x(), (int) lastPan.y())); + QPointF s2 = mapToScene(new QPoint((int) event.pos().x(), (int) event.pos().y())); + QPointF delta = new QPointF(s1.x() - s2.x(), s1.y() - s2.y()); + lastPan = event.pos(); + // Scroll the scrollbars ie. do the pan + double zoom = this.matrix().m11(); + this.horizontalScrollBar().setValue((int) (this.horizontalScrollBar().value() + zoom * delta.x())); + this.verticalScrollBar().setValue((int) (this.verticalScrollBar().value() + zoom * delta.y())); + } + } + super.mouseMoveEvent(event); + } + + /** + * This method is called when the mouse wheel or scroll is used. In this case, + * it allows the user to zoom in and out of the array of tiles. + */ + public void wheelEvent(QWheelEvent event) { + // Get the position of the mouse before scaling, in scene coords + QPointF pointBeforeScale = mapToScene(event.pos()); + + // Scale the view ie. do the zoom + double zoom = this.matrix().m11(); + if (event.delta() > 0) { + // Zoom in (if not at limit) + if (zoom < zoomMax) + scale(scaleFactor, scaleFactor); + } else { + // Zoom out (if not at limit) + if (zoom > zoomMin) + scale(1.0 / scaleFactor, 1.0 / scaleFactor); + } + + // Read the new zoom value + zoom = this.matrix().m11(); + + // Get the position after scaling, in scene coords + QPointF pointAfterScale = mapToScene(event.pos()); + + // Get the offset of how the screen moved + QPointF offset = new QPointF(pointBeforeScale.x() - pointAfterScale.x(), + pointBeforeScale.y() - pointAfterScale.y()); + this.horizontalScrollBar().setValue((int) (this.horizontalScrollBar().value() + zoom * offset.x())); + this.verticalScrollBar().setValue((int) (this.verticalScrollBar().value() + zoom * offset.y())); + } + + /** + * This method gets called when a key on the keyboard is pressed. In this case, + * if the '=' key is pressed, it zooms in. If the '-' key is pressed, it zooms + * out. + */ + public void keyPressEvent(QKeyEvent event) { + double scaleFactor = 1.15; + if (event.key() == Key.Key_Equal.value()) { + // Zoom in (if not at limit) + if (this.matrix().m11() < zoomMax) + scale(scaleFactor, scaleFactor); + } else if (event.key() == Key.Key_Minus.value()) { + // Zoom out (if not at limit) + if (this.matrix().m11() > zoomMin) + scale(1.0 / scaleFactor, 1.0 / scaleFactor); + } + } + + public void zoomIn() { + // Zoom in (if not at limit) + if (this.matrix().m11() < zoomMax) + scale(scaleFactor, scaleFactor); + } + + public void zoomOut() { + // Zoom out (if not at limit) + if (this.matrix().m11() > zoomMin) + scale(1.0 / scaleFactor, 1.0 / scaleFactor); + } +} \ No newline at end of file From 1723d81d1ada2a8c9e3df61f5c472a8fb7605860 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 7 Oct 2025 14:03:57 -0600 Subject: [PATCH 02/32] Fix minor issues Signed-off-by: Chris Lavin --- .../rapidwright/gui/SchematicScene.java | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index a1a41a904..42fc5f60f 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -28,7 +28,9 @@ 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; @@ -92,7 +94,7 @@ public class SchematicScene extends QGraphicsScene { private static QFontMetrics fm = new QFontMetrics(FONT); private static QBrush canvasBackgroundBrush = new QBrush(QColor.white); - private static final QBrush PORT_BRUSH = BLACK_BRUSH; + 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; @@ -391,7 +393,7 @@ private static QPolygonF createPortShape(ElkNode topPort, boolean isOutput) { double y = portY + (height - TOP_PORT_HEIGHT - PORT_MARGIN) / 2.0; QPolygonF portShape = new QPolygonF(); - double pointDist = TOP_PORT_HEIGHT * 0.3; // Pointy part of the port + double pointDist = TOP_PORT_HEIGHT * 0.2; // Pointy part of the port if (isOutput) { // Point to the left @@ -399,14 +401,14 @@ private static QPolygonF createPortShape(ElkNode topPort, boolean isOutput) { 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 + pointDist, y + TOP_PORT_HEIGHT)); - portShape.add(new QPointF(x + pointDist, y + (TOP_PORT_HEIGHT / 2))); + portShape.add(new QPointF(x - pointDist, 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 - pointDist, y)); - portShape.add(new QPointF(x + TOP_PORT_WIDTH, y + (TOP_PORT_HEIGHT / 2))); + portShape.add(new QPointF(x + TOP_PORT_WIDTH + pointDist, y + (TOP_PORT_HEIGHT / 2))); portShape.add(new QPointF(x + TOP_PORT_WIDTH - pointDist, y + TOP_PORT_HEIGHT)); - portShape.add(new QPointF(x, y + (TOP_PORT_HEIGHT / 2))); + portShape.add(new QPointF(x, y + TOP_PORT_HEIGHT)); } return portShape; @@ -425,6 +427,14 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { elkTopPort.setIdentifier(portInstName); elkTopPort.setParent(parent); parent.getChildren().add(elkTopPort); + ElkPort topElkPort = f.createElkPort(); + topElkPort.setParent(elkTopPort); + topElkPort.setIdentifier(portInstName); + topElkPort.setProperty(CoreOptions.PORT_SIDE, topPort.isOutput() ? PortSide.EAST : PortSide.WEST); + topElkPort.setProperty(CoreOptions.PORT_INDEX, 1); + topElkPort.setDimensions(PORT_SIZE, PORT_SIZE); + portInstMap.put(portInst, topElkPort); + elkTopPort.getPorts().add(topElkPort); } } @@ -445,35 +455,22 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { labelElkNode(elkInst, inst.getCellName()); // Create ports - List westPorts = new ArrayList<>(); - List eastPorts = new ArrayList<>(); + Map westPorts = new TreeMap<>(); + Map eastPorts = new TreeMap<>(); double longestWestName = 0; double longestEastName = 0; for (EDIFPort port : inst.getCellPorts()) { - if (port.isBus()) { - for (int i : port.getBitBlastedIndicies()) { - String name = port.getPortInstNameFromPort(i); - if (port.isInput()) { - westPorts.add(name); - longestWestName = Math.max(longestWestName, fm.width(name)); - } else { - eastPorts.add(name); - longestEastName = Math.max(longestEastName, fm.width(name)); - } - } - } else { - String name = port.getPortInstNameFromPort(0); + for (int i : port.isBus() ? port.getBitBlastedIndicies() : new int[] { 0 }) { + String name = port.getPortInstNameFromPort(i); if (port.isInput()) { - westPorts.add(name); + westPorts.put(name, inst.getPortInst(name)); longestWestName = Math.max(longestWestName, fm.width(name)); } else { - eastPorts.add(name); + eastPorts.put(name, inst.getPortInst(name)); longestEastName = Math.max(longestEastName, fm.width(name)); } } } - Collections.sort(westPorts); - Collections.sort(eastPorts); int portStartIndex = 0; portStartIndex = createElkPorts(portStartIndex, isHierCell, elkInst, westPorts, PortSide.WEST); @@ -512,14 +509,13 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { ElkPort sink = getOrCreateElkPort(s, prefix, instNodeMap); if (driver == null || sink == null) continue; - if (driver.getParent() == parent && sink.getParent() == parent) { - ElkEdge edge = ElkGraphFactory.eINSTANCE.createElkEdge(); - edge.setContainingNode(parent); - edge.setIdentifier(prefix + net.getName()); - edge.getSources().add(driver); - edge.getTargets().add(sink); - parent.getContainedEdges().add(edge); - } + + ElkEdge edge = ElkGraphFactory.eINSTANCE.createElkEdge(); + edge.setContainingNode(parent); + edge.setIdentifier(prefix + net.getName()); + edge.getSources().add(driver); + edge.getTargets().add(sink); + parent.getContainedEdges().add(edge); } } } @@ -545,19 +541,20 @@ private ElkPort getOrCreateElkPort(EDIFPortInst p, String prefix, Map portNames, + private int createElkPorts(int startIdx, boolean isHierCell, ElkNode parent, Map portNames, PortSide side) { - for (String name : portNames) { + for (Entry e : portNames.entrySet()) { ElkPort port = ElkGraphFactory.eINSTANCE.createElkPort(); + portInstMap.put(e.getValue(), port); port.setParent(parent); parent.getPorts().add(port); - port.setIdentifier(name); + 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, name); + labelElkNode(port, e.getKey()); } } From dac59d5161c3c3da720d69f2376f21adc6912d67 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 7 Oct 2025 14:46:28 -0600 Subject: [PATCH 03/32] Fixes minor rendering issues Signed-off-by: Chris Lavin --- .../rapidwright/gui/SchematicScene.java | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 42fc5f60f..267ae9b24 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -114,13 +114,14 @@ public class SchematicScene extends QGraphicsScene { private static final double MIN_NODE_HEIGHT = 20.0; private static final double MIN_NODE_WIDTH = 40.0; - private static final double PORT_HEIGHT = 34.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_MARGIN = 20.0; private static final double PORT_LABEL_SPACING = 4.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; @@ -178,7 +179,7 @@ public void renderSchematic() { portLabel.setBrush(PORT_BRUSH); portLabel.setFont(FONT); double portShapeX = topPort.getX() + (topPort.getWidth() - TOP_PORT_WIDTH) / 2.0; - double portShapeY = topPort.getY() + (topPort.getHeight() - TOP_PORT_HEIGHT - PORT_MARGIN) / 2.0; + double portShapeY = topPort.getY() + (topPort.getHeight() - TOP_PORT_HEIGHT) / 2.0; double labelX = portShapeX + (TOP_PORT_WIDTH - portLabel.boundingRect().width()) / 2.0; double labelY = portShapeY + TOP_PORT_HEIGHT + PORT_LABEL_SPACING; portLabel.setPos(labelX, labelY); @@ -332,7 +333,7 @@ private void renderEdges(ElkNode parent) { double endY = s.getEndY(); if (!e.getSources().isEmpty()) { - ElkPort srcPort = (ElkPort) s; + ElkPort srcPort = (ElkPort) e.getSources().get(0); ElkNode portParent = (ElkNode) srcPort.getParent(); EDIFPortInst portInst = elkNodeTopPortMap.get(portParent); if (portInst != null && portInst.isTopLevelPort()) { @@ -355,20 +356,16 @@ private void renderEdges(ElkNode parent) { double lastX = startX; double lastY = startY; + String id = e.getIdentifier(); + String lookup = "NET:" + (id == null ? "" : id); for (ElkBendPoint bp : s.getBendPoints()) { - String id = e.getIdentifier(); - String lookup = "NET:" + (id == null ? "" : id); - QGraphicsLineItem line = addLine(lastX, lastY, bp.getX(), bp.getY(), NET_PEN); - line.setData(0, lookup); - line.setZValue(0); - lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(line); - QGraphicsLineItem clickLine = addLine(lastX, lastY, bp.getX(), bp.getY(), NET_CLICK_PEN); - clickLine.setData(0, lookup); - clickLine.setData(1, "CLICK"); - lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(clickLine); + drawSegment(lastX, lastY, bp.getX(), bp.getY(), lookup); lastX = bp.getX(); lastY = bp.getY(); } + + // Draw final segment + drawSegment(lastX, lastY, endX, endY, lookup); } for (ElkNode child : parent.getChildren()) { @@ -378,10 +375,21 @@ private void renderEdges(ElkNode parent) { } } + 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"); + 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 - PORT_MARGIN) / 2.0; - return new QPointF(portX + (isOutput ? 0 : TOP_PORT_WIDTH), portY + TOP_PORT_HEIGHT / 2); + 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) { @@ -390,24 +398,23 @@ private static QPolygonF createPortShape(ElkNode topPort, boolean isOutput) { double height = topPort.getHeight(); double width = topPort.getWidth(); double x = portX + (width - TOP_PORT_WIDTH) / 2.0; - double y = portY + (height - TOP_PORT_HEIGHT - PORT_MARGIN) / 2.0; + double y = portY + (height - TOP_PORT_HEIGHT) / 2.0; QPolygonF portShape = new QPolygonF(); - double pointDist = TOP_PORT_HEIGHT * 0.2; // Pointy part of the port if (isOutput) { // Point to the left - portShape.add(new QPointF(x + pointDist, y)); + 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 + pointDist, y + TOP_PORT_HEIGHT)); - portShape.add(new QPointF(x - pointDist, y + (TOP_PORT_HEIGHT / 2))); + 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 - pointDist, y)); - portShape.add(new QPointF(x + TOP_PORT_WIDTH + pointDist, y + (TOP_PORT_HEIGHT / 2))); - portShape.add(new QPointF(x + TOP_PORT_WIDTH - pointDist, y + TOP_PORT_HEIGHT)); + 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)); } @@ -423,7 +430,7 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { ElkNode elkTopPort = f.createElkNode(); EDIFPortInst portInst = topPort.getInternalPortInstFromIndex(i); elkNodeTopPortMap.put(elkTopPort, portInst); - elkTopPort.setDimensions(TOP_PORT_WIDTH, TOP_PORT_WIDTH); + elkTopPort.setDimensions(TOP_PORT_WIDTH + POINT_DIST, TOP_PORT_HEIGHT); elkTopPort.setIdentifier(portInstName); elkTopPort.setParent(parent); parent.getChildren().add(elkTopPort); From 81dfd06b8d0be2d56864bd5c7164cba301d26ea0 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 7 Oct 2025 15:18:48 -0600 Subject: [PATCH 04/32] Fixes around labeling and refactoring Signed-off-by: Chris Lavin --- .../rapidwright/gui/SchematicScene.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 267ae9b24..a03ede972 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -35,6 +36,7 @@ import org.eclipse.elk.core.IGraphLayoutEngine; import org.eclipse.elk.core.RecursiveGraphLayoutEngine; 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; @@ -427,21 +429,24 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { for (EDIFPort topPort : cell.getPorts()) { for (int i : (topPort.isBus() ? topPort.getBitBlastedIndicies() : new int[] { 0 })) { String portInstName = topPort.getPortInstNameFromPort(0); - ElkNode elkTopPort = f.createElkNode(); + ElkNode elkTopPortNode = f.createElkNode(); EDIFPortInst portInst = topPort.getInternalPortInstFromIndex(i); - elkNodeTopPortMap.put(elkTopPort, portInst); - elkTopPort.setDimensions(TOP_PORT_WIDTH + POINT_DIST, TOP_PORT_HEIGHT); + elkNodeTopPortMap.put(elkTopPortNode, portInst); + 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.setParent(parent); - parent.getChildren().add(elkTopPort); - ElkPort topElkPort = f.createElkPort(); - topElkPort.setParent(elkTopPort); - topElkPort.setIdentifier(portInstName); - topElkPort.setProperty(CoreOptions.PORT_SIDE, topPort.isOutput() ? PortSide.EAST : PortSide.WEST); - topElkPort.setProperty(CoreOptions.PORT_INDEX, 1); - topElkPort.setDimensions(PORT_SIZE, PORT_SIZE); - portInstMap.put(portInst, topElkPort); - elkTopPort.getPorts().add(topElkPort); + 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(portInst, elkTopPort); + elkTopPortNode.getPorts().add(elkTopPort); } } @@ -458,6 +463,7 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { boolean isHierCell = !inst.getCellType().isLeafCellOrBlackBox(); // Create labels + elkInst.setProperty(CoreOptions.NODE_LABELS_PLACEMENT, NodeLabelPlacement.outsideTopCenter()); labelElkNode(elkInst, inst.getName()); labelElkNode(elkInst, inst.getCellName()); From 255afe0f32d7d193ea8d8d69c579c7da0c3a2607 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 7 Oct 2025 16:24:01 -0600 Subject: [PATCH 05/32] Fix top port labels Signed-off-by: Chris Lavin --- .../xilinx/rapidwright/gui/SchematicScene.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index a03ede972..afa2f709a 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -165,7 +165,7 @@ public void renderSchematic() { setSceneRect(new QRectF(new QPointF(0, 0), size)); - // Top ports are treated as ElkNodes + // We create arrow-shaped top port ElkNodes to serve as targets for top ports for (ElkNode topPort : elkRoot.getChildren()) { EDIFPortInst portInst = elkNodeTopPortMap.get(topPort); if (portInst != null) { @@ -178,12 +178,19 @@ public void renderSchematic() { lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(port); QGraphicsSimpleTextItem portLabel = addSimpleText(portInst.getName()); - portLabel.setBrush(PORT_BRUSH); + 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 + (TOP_PORT_WIDTH - portLabel.boundingRect().width()) / 2.0; - double labelY = portShapeY + TOP_PORT_HEIGHT + PORT_LABEL_SPACING; + double labelX = portShapeX; + if (portInst.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); } From 205a841e31251005eaf0a482af30deee0c779914 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Mon, 13 Oct 2025 11:13:26 -0600 Subject: [PATCH 06/32] Fix hier-cell pin labels Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/gui/SchematicScene.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index afa2f709a..29da5573f 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -270,7 +270,7 @@ private void renderNode(ElkNode parent) { if (!isLeaf) { labelX += side == PortSide.EAST ? child.getWidth() + LABEL_BUFFER : - textWidth - LABEL_BUFFER; - labelY += y - textHeight - 2*LABEL_BUFFER; + labelY = y - textHeight + LABEL_BUFFER; } else { labelX += side == PortSide.EAST ? child.getWidth() - textWidth - 2*LABEL_BUFFER : 2*LABEL_BUFFER; labelY = y - textHeight / 2.0; From e76f413b19f9aeb19776ee780b3919843332b722 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Mon, 13 Oct 2025 22:09:41 -0600 Subject: [PATCH 07/32] Fix hier button, still needs work for internal routes Signed-off-by: Chris Lavin --- .../rapidwright/gui/SchematicScene.java | 159 +++++++++++------- 1 file changed, 98 insertions(+), 61 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 29da5573f..cad2bcac9 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -58,11 +58,13 @@ 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; @@ -86,6 +88,7 @@ public class SchematicScene extends QGraphicsScene { private Map elkNodeTopPortMap = new HashMap<>(); private Map elkNodeCellMap = new HashMap<>(); private Map> lookupMap = new HashMap<>(); + private Set expandedCellInsts = new HashSet<>(); private static QFont FONT = new QFont("Arial", 8); private static QFont BUTTON_TEXT_FONT = new QFont("Arial", 10, QFont.Weight.Bold.value()); @@ -129,6 +132,7 @@ public class SchematicScene extends QGraphicsScene { 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"; public SchematicScene(EDIFNetlist netlist) { super(); @@ -196,11 +200,11 @@ public void renderSchematic() { } } - renderNode(elkRoot); - renderEdges(elkRoot); + renderNode(elkRoot, 0, 0); + renderEdges(elkRoot, 0, 0); } - private void renderNode(ElkNode parent) { + private void renderNode(ElkNode parent, double xOffset, double yOffset) { EDIFCell cell = elkNodeCellMap.get(parent); for (ElkNode child : parent.getChildren()) { if (elkNodeTopPortMap.containsKey(child)) { @@ -210,26 +214,27 @@ private void renderNode(ElkNode parent) { EDIFCellInst eci = cell.getCellInst(child.getIdentifier()); boolean isLeaf = true; - boolean isExpanded = false; + String cellInstName = elkRoot.equals(parent) ? (eci != null ? eci.getName() : "") : child.getIdentifier(); + 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(); + if (isLeaf) { - addRect(child.getX(), child.getY(), child.getWidth(), child.getHeight(), CELL_PEN, CELL_BRUSH); + addRect(x, y, child.getWidth(), child.getHeight(), CELL_PEN, CELL_BRUSH); } else { String expandedCellName = !elkRoot.equals(parent) ? child.getIdentifier() : eci.getName(); if (isExpanded) { - // TODO - addRect(child.getX(), child.getY(), child.getWidth(), child.getHeight(), - EXPANDED_HIER_CELL_PEN, EXPANDED_HIER_CELL_BRUSH); + addRect(x, y, child.getWidth(), child.getHeight(), EXPANDED_HIER_CELL_PEN, EXPANDED_HIER_CELL_BRUSH); } else { - addRect(child.getX(), child.getY(), child.getWidth(), child.getHeight(), - HIER_CELL_PEN, HIER_CELL_BRUSH); + addRect(x, y, child.getWidth(), child.getHeight(), HIER_CELL_PEN, HIER_CELL_BRUSH); } - createHierButton(child, isExpanded, expandedCellName); + createHierButton(child, isExpanded, expandedCellName, xOffset, yOffset); } @@ -240,8 +245,8 @@ private void renderNode(ElkNode parent) { QGraphicsSimpleTextItem instLabel = addSimpleText(instNameLabel.getText()); instLabel.setBrush(BLACK_BRUSH); instLabel.setFont(FONT); - double instLabelX = child.getX() + (child.getWidth() - instLabel.boundingRect().width()) / 2.0; - double instLabelY = child.getY() - instLabel.boundingRect().height(); + 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); @@ -249,15 +254,15 @@ private void renderNode(ElkNode parent) { QGraphicsSimpleTextItem cellLabel = addSimpleText(cellTypeLabel.getText()); cellLabel.setBrush(BLACK_BRUSH); cellLabel.setFont(FONT); - double cellLabelX = child.getX() + (child.getWidth() - cellLabel.boundingRect().width()) / 2.0; - double cellLabelY = child.getY() + child.getHeight(); + 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 y = child.getY() + port.getY() + port.getHeight() / 2.0; + double yPort = y + port.getY() + port.getHeight() / 2.0; PortSide side = port.getProperty(CoreOptions.PORT_SIDE); - drawPin(child, port, y, side, isExpanded); + drawPin(child, port, yPort, side, isExpanded, xOffset); QGraphicsSimpleTextItem pinLabel = addSimpleText(port.getIdentifier()); pinLabel.setBrush(BLACK_BRUSH); @@ -265,28 +270,28 @@ private void renderNode(ElkNode parent) { pinLabel.setZValue(6); double textWidth = pinLabel.boundingRect().width(); double textHeight = pinLabel.boundingRect().height(); - double labelX = child.getX(); - double labelY = child.getY(); + double labelX = x; + double labelY = y; if (!isLeaf) { labelX += side == PortSide.EAST ? child.getWidth() + LABEL_BUFFER : - textWidth - LABEL_BUFFER; - labelY = y - textHeight + LABEL_BUFFER; + labelY = yPort - textHeight + LABEL_BUFFER; } else { labelX += side == PortSide.EAST ? child.getWidth() - textWidth - 2*LABEL_BUFFER : 2*LABEL_BUFFER; - labelY = y - textHeight / 2.0; + labelY = yPort - textHeight / 2.0; } pinLabel.setPos(labelX, labelY); } if (child.getChildren().size() > 0) { - renderNode(child); + renderNode(child, x, y); } } } - private void drawPin(ElkNode cell, ElkPort port, double y, PortSide side, boolean isExpanded) { - double x1 = cell.getX() + (side == PortSide.EAST ? cell.getWidth() : -PIN_LINE_LENGTH); - double x2 = cell.getX() + (side == PortSide.EAST ? cell.getWidth() + PIN_LINE_LENGTH : 0); + private void drawPin(ElkNode cell, ElkPort port, double y, PortSide side, boolean isExpanded, double xOffset) { + 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); @@ -297,8 +302,8 @@ private void drawPin(ElkNode cell, ElkPort port, double y, PortSide side, boolea if (isExpanded) { // Draw inner pins - x1 = cell.getX() + (side == PortSide.EAST ? cell.getWidth() - PIN_LINE_LENGTH : 0); - x2 = cell.getX() + (side == PortSide.EAST ? cell.getWidth() : PIN_LINE_LENGTH); + 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 @@ -307,16 +312,18 @@ private void drawPin(ElkNode cell, ElkPort port, double y, PortSide side, boolea } } - private QGraphicsPathItem createHierButton(ElkNode node, boolean isExpanded, String expandedCellName) { - double buttonX = node.getX() + (node.getWidth() - BUTTON_SIZE) / 2.0; - double buttonY = node.getY() + (node.getHeight() - BUTTON_SIZE) / 2.0; + private QGraphicsPathItem createHierButton(ElkNode node, boolean isExpanded, String expandedCellName, double xOffset, double yOffset) { + double buttonX = xOffset + node.getX() + (node.getWidth() - BUTTON_SIZE) / 2.0; + double buttonY = yOffset + node.getY() + (node.getHeight() - BUTTON_SIZE) / 2.0; 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); - button.setData(0, "HIER_BUTTON: " + expandedCellName + " : " + (isExpanded ? "COLLAPSE" : "EXPAND")); - button.setToolTip(isExpanded ? "Collapse" : "Expand"); + 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 @@ -327,19 +334,21 @@ private QGraphicsPathItem createHierButton(ElkNode node, boolean isExpanded, Str 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) { + 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 = s.getStartX(); - double startY = s.getStartY(); - double endX = s.getEndX(); - double endY = s.getEndY(); + 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); @@ -369,8 +378,8 @@ private void renderEdges(ElkNode parent) { String lookup = "NET:" + (id == null ? "" : id); for (ElkBendPoint bp : s.getBendPoints()) { drawSegment(lastX, lastY, bp.getX(), bp.getY(), lookup); - lastX = bp.getX(); - lastY = bp.getY(); + lastX = xOffset + bp.getX(); + lastY = yOffset + bp.getY(); } // Draw final segment @@ -379,7 +388,7 @@ private void renderEdges(ElkNode parent) { for (ElkNode child : parent.getChildren()) { if (child.getChildren().size() > 0) { - renderEdges(child); + renderEdges(child, child.getX() + xOffset, child.getY() + yOffset); } } } @@ -433,27 +442,30 @@ private static QPolygonF createPortShape(ElkNode topPort, boolean isOutput) { private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { ElkGraphFactory f = ElkGraphFactory.eINSTANCE; - for (EDIFPort topPort : cell.getPorts()) { - for (int i : (topPort.isBus() ? topPort.getBitBlastedIndicies() : new int[] { 0 })) { - String portInstName = topPort.getPortInstNameFromPort(0); - ElkNode elkTopPortNode = f.createElkNode(); - EDIFPortInst portInst = topPort.getInternalPortInstFromIndex(i); - elkNodeTopPortMap.put(elkTopPortNode, portInst); - 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(portInst, elkTopPort); - elkTopPortNode.getPorts().add(elkTopPort); + // 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(0); + ElkNode elkTopPortNode = f.createElkNode(); + EDIFPortInst portInst = topPort.getInternalPortInstFromIndex(i); + elkNodeTopPortMap.put(elkTopPortNode, portInst); + 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(portInst, elkTopPort); + elkTopPortNode.getPorts().add(elkTopPort); + } } } @@ -501,6 +513,10 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { 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())) { + populateCellContent(inst.getCellType(), elkInst, prefix + inst.getName() + "/"); + } } for (EDIFNet net : cell.getNets()) { @@ -588,4 +604,25 @@ private void labelElkNode(ElkGraphElement n, String name) { 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()); + } + } + super.mousePressEvent(event); + } + + private void toggleCellInstExpansion(String cellInstName) { + if (expandedCellInsts.contains(cellInstName)) { + expandedCellInsts.remove(cellInstName); + } else { + expandedCellInsts.add(cellInstName); + } + drawCell(currCell); + } } From 5d03f3071bb3189e8e56a80d79e21555a366421d Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 14 Oct 2025 09:38:53 -0600 Subject: [PATCH 08/32] Fixes nets inside expanded cells Signed-off-by: Chris Lavin --- .../rapidwright/gui/SchematicScene.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index cad2bcac9..b7ca4526e 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -377,9 +377,11 @@ private void renderEdges(ElkNode parent, double xOffset, double yOffset) { String id = e.getIdentifier(); String lookup = "NET:" + (id == null ? "" : id); for (ElkBendPoint bp : s.getBendPoints()) { - drawSegment(lastX, lastY, bp.getX(), bp.getY(), lookup); - lastX = xOffset + bp.getX(); - lastY = yOffset + bp.getY(); + double bpX = bp.getX() + xOffset; + double bpY = bp.getY() + yOffset; + drawSegment(lastX, lastY, bpX, bpY, lookup); + lastX = bpX; + lastY = bpY; } // Draw final segment @@ -446,7 +448,7 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { if (prefix.isEmpty()) { for (EDIFPort topPort : cell.getPorts()) { for (int i : (topPort.isBus() ? topPort.getBitBlastedIndicies() : new int[] { 0 })) { - String portInstName = topPort.getPortInstNameFromPort(0); + String portInstName = topPort.getPortInstNameFromPort(i); ElkNode elkTopPortNode = f.createElkNode(); EDIFPortInst portInst = topPort.getInternalPortInstFromIndex(i); elkNodeTopPortMap.put(elkTopPortNode, portInst); @@ -515,6 +517,7 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { elkInst.setDimensions(width, height); if (isHierCell && expandedCellInsts.contains(prefix + inst.getName())) { + createExpandedCellInnerPorts(inst); populateCellContent(inst.getCellType(), elkInst, prefix + inst.getName() + "/"); } } @@ -557,6 +560,20 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { } } + private void createExpandedCellInnerPorts(EDIFCellInst inst) { + for (EDIFPort port : inst.getCellPorts()) { + for (int i : (port.isBus() ? port.getBitBlastedIndicies() : new int[] { 0 })) { + EDIFPortInst outerPortInst = inst.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); + portInstMap.put(innerPortInst, outerElkPort); + } + } + } + } + private ElkPort getOrCreateElkPort(EDIFPortInst p, String prefix, Map instNodeMap) { ElkPort port = portInstMap.get(p); if (port == null) { From 13872efac4020c17d105307d5bab7dabdd3c7610 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 14 Oct 2025 09:52:43 -0600 Subject: [PATCH 09/32] Move button to top left corner of expanded cells Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/gui/SchematicScene.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index b7ca4526e..52d351c61 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -313,8 +313,8 @@ private void drawPin(ElkNode cell, ElkPort port, double y, PortSide side, boolea } private QGraphicsPathItem createHierButton(ElkNode node, boolean isExpanded, String expandedCellName, double xOffset, double yOffset) { - double buttonX = xOffset + node.getX() + (node.getWidth() - BUTTON_SIZE) / 2.0; - double buttonY = yOffset + node.getY() + (node.getHeight() - BUTTON_SIZE) / 2.0; + 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); From b0681dc069b16fe56624aa19ed20f30145cb0f62 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 14 Oct 2025 10:32:49 -0600 Subject: [PATCH 10/32] Fix padding/spacing around labels and buttons Signed-off-by: Chris Lavin --- .../rapidwright/gui/SchematicScene.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 52d351c61..f02ee2b1f 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -35,6 +35,7 @@ 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; @@ -125,6 +126,11 @@ public class SchematicScene extends QGraphicsScene { 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; @@ -143,11 +149,16 @@ public SchematicScene(EDIFNetlist netlist) { private ElkNode createElkRoot(EDIFCell cell) { ElkNode root = ElkGraphFactory.eINSTANCE.createElkNode(); root.setIdentifier(cell.getName()); - root.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered"); - + 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(EDIFCell cell) { clear(); this.currCell = cell; @@ -517,6 +528,14 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { 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(inst); populateCellContent(inst.getCellType(), elkInst, prefix + inst.getName() + "/"); } From da2a488941f59d9ec31fb6461de2cd9934d5b0c4 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 14 Oct 2025 15:09:08 -0600 Subject: [PATCH 11/32] Fixing multiple hier view, switch from EDIFCell->EDIFHierCellInst Signed-off-by: Chris Lavin --- .../rapidwright/gui/NetlistBrowser.java | 6 ++- .../rapidwright/gui/SchematicScene.java | 42 ++++++++++++------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java index dbeb4ec86..97622d5da 100644 --- a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java +++ b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java @@ -33,6 +33,8 @@ 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.EDIFNetlist; import com.xilinx.rapidwright.edif.EDIFTools; @@ -115,8 +117,8 @@ private void init() { public void selectNetlistItem(QModelIndex index) { QTreeWidgetItem item = treeWidget.getItemFromIndex(index); if (item instanceof HierCellInstTreeWidgetItem) { - EDIFCell cell = ((HierCellInstTreeWidgetItem) item).getInst().getCellType(); - schematicScene.drawCell(cell); + EDIFHierCellInst cellInst = ((HierCellInstTreeWidgetItem) item).getInst(); + schematicScene.drawCell(cellInst); } } diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index f02ee2b1f..c12f59532 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -72,6 +72,7 @@ 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.EDIFNet; import com.xilinx.rapidwright.edif.EDIFNetlist; import com.xilinx.rapidwright.edif.EDIFPort; @@ -81,13 +82,13 @@ public class SchematicScene extends QGraphicsScene { private EDIFNetlist netlist; - private EDIFCell currCell; + private EDIFHierCellInst currCellInst; private ElkNode elkRoot; private Map portInstMap = new HashMap<>(); private Map elkNodeTopPortMap = new HashMap<>(); - private Map elkNodeCellMap = new HashMap<>(); + private Map elkNodeCellMap = new HashMap<>(); private Map> lookupMap = new HashMap<>(); private Set expandedCellInsts = new HashSet<>(); @@ -146,9 +147,9 @@ public SchematicScene(EDIFNetlist netlist) { setBackgroundBrush(canvasBackgroundBrush); } - private ElkNode createElkRoot(EDIFCell cell) { + private ElkNode createElkRoot(EDIFHierCellInst cellInst) { ElkNode root = ElkGraphFactory.eINSTANCE.createElkNode(); - root.setIdentifier(cell.getName()); + root.setIdentifier(cellInst.toString()); applyElkNodeProperties(root); return root; } @@ -159,12 +160,16 @@ private void applyElkNodeProperties(ElkNode root) { root.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_TO_NODE_SPACING); } - public void drawCell(EDIFCell cell) { + public void drawCell(EDIFHierCellInst cellInst) { clear(); - this.currCell = cell; - elkRoot = createElkRoot(cell); - populateCellContent(cell, elkRoot, ""); - elkNodeCellMap.put(elkRoot, cell); + portInstMap.clear(); + elkNodeTopPortMap.clear(); + elkNodeCellMap.clear(); + lookupMap.clear(); + this.currCellInst = cellInst; + elkRoot = createElkRoot(cellInst); + populateCellContent(cellInst, elkRoot, ""); + elkNodeCellMap.put(elkRoot, cellInst); IGraphLayoutEngine engine = new RecursiveGraphLayoutEngine(); IElkProgressMonitor monitor = new BasicProgressMonitor(); @@ -216,13 +221,18 @@ public void renderSchematic() { } private void renderNode(ElkNode parent, double xOffset, double yOffset) { - EDIFCell cell = elkNodeCellMap.get(parent); + EDIFCell cell = elkNodeCellMap.get(parent).getCellType(); for (ElkNode child : parent.getChildren()) { if (elkNodeTopPortMap.containsKey(child)) { // Rendered in renderSchematic() continue; } - EDIFCellInst eci = cell.getCellInst(child.getIdentifier()); + String relCellInstName = child.getIdentifier(); + if (relCellInstName.startsWith(parent.getIdentifier())) { + // Remove hierarchical reference + relCellInstName = relCellInstName.substring(parent.getIdentifier().length() + 1); + } + EDIFCellInst eci = cell.getCellInst(relCellInstName); boolean isLeaf = true; String cellInstName = elkRoot.equals(parent) ? (eci != null ? eci.getName() : "") : child.getIdentifier(); @@ -452,8 +462,9 @@ private static QPolygonF createPortShape(ElkNode topPort, boolean isOutput) { return portShape; } - private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { + 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()) { @@ -490,7 +501,8 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { elkInst.setIdentifier(prefix + inst.getName()); elkInst.setProperty(CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_ORDER); instNodeMap.put(inst, elkInst); - elkNodeCellMap.put(elkInst, inst.getCellType()); + EDIFHierCellInst childInst = cellInst.getChild(inst); + elkNodeCellMap.put(elkInst, childInst); boolean isHierCell = !inst.getCellType().isLeafCellOrBlackBox(); @@ -537,7 +549,7 @@ private void populateCellContent(EDIFCell cell, ElkNode parent, String prefix) { SIDE_PADDING // Side )); createExpandedCellInnerPorts(inst); - populateCellContent(inst.getCellType(), elkInst, prefix + inst.getName() + "/"); + populateCellContent(childInst, elkInst, prefix + inst.getName() + "/"); } } @@ -659,6 +671,6 @@ private void toggleCellInstExpansion(String cellInstName) { } else { expandedCellInsts.add(cellInstName); } - drawCell(currCell); + drawCell(currCellInst); } } From df3d10166c8be2343af300bf30ce83e67a9df58e Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Fri, 7 Nov 2025 18:25:08 -0700 Subject: [PATCH 12/32] Basic selection using Strings, still WIP Signed-off-by: Chris Lavin --- .../rapidwright/gui/NetlistBrowser.java | 18 +++++ .../rapidwright/gui/NetlistTreeWidget.java | 27 +++++-- .../rapidwright/gui/SchematicScene.java | 80 +++++++++++++++++-- 3 files changed, 115 insertions(+), 10 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java index 97622d5da..29033d3f1 100644 --- a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java +++ b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java @@ -35,7 +35,9 @@ 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 { @@ -112,6 +114,8 @@ private void init() { schematicWidget.setWidget(schematicView); schematicWidget.setFeatures(DockWidgetFeature.DockWidgetMovable); addDockWidget(DockWidgetArea.RightDockWidgetArea, schematicWidget); + + schematicScene.objectSelected.connect(this, "selectFromSchematic(String)"); } public void selectNetlistItem(QModelIndex index) { @@ -119,6 +123,20 @@ public void selectNetlistItem(QModelIndex index) { if (item instanceof HierCellInstTreeWidgetItem) { EDIFHierCellInst cellInst = ((HierCellInstTreeWidgetItem) item).getInst(); schematicScene.drawCell(cellInst); + // TODO We need to check the current parent to see if that we can select in the schematic + } 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 5bfd07ecc..8ea748748 100644 --- a/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java +++ b/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java @@ -26,6 +26,7 @@ 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; @@ -46,7 +47,7 @@ public class NetlistTreeWidget extends QTreeWidget { private QTreeWidgetItem rootItem; - private HashMap instLookup = new HashMap<>(); + private Map objectLookup = new HashMap<>(); private static final String DUMMY = "_*DUMMY*_"; @@ -72,6 +73,8 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i EDIFCell cell = inst.getCellType(); curr.setText(0, inst.getInst().getName() + " (" + cell.getName() + ")"); + String hierPrefix = inst.toString(); + QTreeWidgetItem ports = new QTreeWidgetItem(curr); ports.setText(0, "Ports (" + cell.getPorts().size() + ")"); List edifPorts = new ArrayList<>(cell.getPorts()); @@ -79,6 +82,10 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i for (EDIFPort port : edifPorts) { QTreeWidgetItem n = new QTreeWidgetItem(ports); n.setText(0, port.getName() + " (" + port.getDirection() + ")"); + n.setData(0, 0, port); + String portLookup = "PORT:" /* + port.getParentCell() + "/" */ + port.getName(); + n.setData(1, 0, portLookup); + objectLookup.put(portLookup, n); } ports.setExpanded(false); @@ -90,6 +97,10 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i for (EDIFNet net : edifNets) { QTreeWidgetItem n = new QTreeWidgetItem(nets); n.setText(0, net.getName()); + n.setData(0, 0, net); + String netLookup = "NET:" + /* hierPrefix + "/" + */ net.getName(); + n.setData(1, 0, netLookup); + objectLookup.put(netLookup, n); } nets.setExpanded(false); @@ -111,6 +122,10 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i for (EDIFCellInst i : leaves) { QTreeWidgetItem leaf = new QTreeWidgetItem(leafCells); leaf.setText(0, i.getName() + " (" + i.getCellName() + ")"); + leaf.setData(0, 0, i); + String leafLookup = "INST:" + /* hierPrefix + "/" + */ i.getName(); + leaf.setData(1, 0, leafLookup); + objectLookup.put(leafLookup, leaf); } leafCells.setExpanded(false); @@ -119,7 +134,10 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i HierCellInstTreeWidgetItem cellInst = new HierCellInstTreeWidgetItem(curr); cellInst.setText(0, i.getInst().getName() + " (" + i.getCellName() + ")"); cellInst.setInst(i); - instLookup.put(i.toString(), cellInst); + cellInst.setData(0, 0, i); + String instLookup = "INST:" + i.toString(); + cellInst.setData(1, 0, instLookup); + objectLookup.put(instLookup, cellInst); QTreeWidgetItem dummy = new QTreeWidgetItem(cellInst); dummy.setText(0, DUMMY); } @@ -153,8 +171,7 @@ public QTreeWidgetItem getItemFromIndex(QModelIndex index) { return this.itemFromIndex(index); } - public HierCellInstTreeWidgetItem getItemByHierInstName(String name) { - return instLookup.get(name); + 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 index c12f59532..ec2c3927d 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -54,6 +54,7 @@ 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; @@ -91,6 +92,9 @@ public class SchematicScene extends QGraphicsScene { 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<>(); private static QFont FONT = new QFont("Arial", 8); private static QFont BUTTON_TEXT_FONT = new QFont("Arial", 10, QFont.Weight.Bold.value()); @@ -105,6 +109,7 @@ public class SchematicScene extends QGraphicsScene { 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); @@ -140,6 +145,7 @@ public class SchematicScene extends QGraphicsScene { 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(); @@ -166,6 +172,7 @@ public void drawCell(EDIFHierCellInst cellInst) { elkNodeTopPortMap.clear(); elkNodeCellMap.clear(); lookupMap.clear(); + selectedObjects.clear(); this.currCellInst = cellInst; elkRoot = createElkRoot(cellInst); populateCellContent(cellInst, elkRoot, ""); @@ -246,18 +253,21 @@ private void renderNode(ElkNode parent, double xOffset, double yOffset) { double x = xOffset + child.getX(); double y = yOffset + child.getY(); + QGraphicsRectItem rect = null; if (isLeaf) { - addRect(x, y, child.getWidth(), child.getHeight(), CELL_PEN, CELL_BRUSH); + rect = addRect(x, y, child.getWidth(), child.getHeight(), CELL_PEN, CELL_BRUSH); } else { String expandedCellName = !elkRoot.equals(parent) ? child.getIdentifier() : eci.getName(); if (isExpanded) { - addRect(x, y, child.getWidth(), child.getHeight(), EXPANDED_HIER_CELL_PEN, EXPANDED_HIER_CELL_BRUSH); + rect = addRect(x, y, child.getWidth(), child.getHeight(), EXPANDED_HIER_CELL_PEN, EXPANDED_HIER_CELL_BRUSH); } else { - addRect(x, y, child.getWidth(), child.getHeight(), HIER_CELL_PEN, HIER_CELL_BRUSH); + rect = addRect(x, y, child.getWidth(), child.getHeight(), HIER_CELL_PEN, HIER_CELL_BRUSH); } createHierButton(child, isExpanded, expandedCellName, xOffset, yOffset); } - + String instLookup = "INST:" + relCellInstName; + rect.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 @@ -423,7 +433,7 @@ private void drawSegment(double lastX, double lastY, double endX, double endY, S 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.setData(1, CLICK); lookupMap.computeIfAbsent(lookup, l -> new ArrayList<>()).add(clickLine); } @@ -660,6 +670,9 @@ public void mousePressEvent(QGraphicsSceneMouseEvent event) { 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); @@ -673,4 +686,61 @@ private void toggleCellInstExpansion(String cellInstName) { } drawCell(currCellInst); } + + 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 guiObjects = lookupMap.get(lookup); + for (Object guiObject : guiObjects == null ? Collections.EMPTY_LIST : guiObjects) { + if (guiObject instanceof QAbstractGraphicsShapeItem) { + QAbstractGraphicsShapeItem shape = (QAbstractGraphicsShapeItem) guiObject; + if (isSelected) { + if (shape.data(1) == null || !shape.data(1).toString().equals(CLICK)) { + shape.setPen(SELECTED_PEN); + } + } else { + if (lookup.startsWith("INST:")) { + shape.setPen(CELL_PEN); + } else if (lookup.startsWith("PORT:")) { + shape.setPen(PORT_PEN); + } + } + } else if (guiObject instanceof QGraphicsLineItem) { + QGraphicsLineItem line = (QGraphicsLineItem) guiObject; + if (line.data(1) == null || !line.data(1).toString().equals(CLICK)) { + line.setPen(isSelected ? SELECTED_PEN : NET_PEN); + } + } + + } + } } From 36d20b37603c3d0e281335b38ab759a9a1da169b Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 12:20:12 -0700 Subject: [PATCH 13/32] Fixes various bugs Signed-off-by: Chris Lavin --- .../rapidwright/gui/NetlistTreeWidget.java | 34 +++++++++---------- .../rapidwright/gui/SchematicScene.java | 28 +++++++++------ 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java b/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java index 8ea748748..190240f71 100644 --- a/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java +++ b/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java @@ -34,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; @@ -72,19 +74,17 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i EDIFCell cell = inst.getCellType(); curr.setText(0, inst.getInst().getName() + " (" + cell.getName() + ")"); - - String hierPrefix = inst.toString(); + 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, port); - String portLookup = "PORT:" /* + port.getParentCell() + "/" */ + port.getName(); + 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); @@ -96,20 +96,21 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i Collections.sort(edifNets); for (EDIFNet net : edifNets) { QTreeWidgetItem n = new QTreeWidgetItem(nets); - n.setText(0, net.getName()); - n.setData(0, 0, net); - String netLookup = "NET:" + /* hierPrefix + "/" + */ net.getName(); + 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)); } @@ -119,11 +120,11 @@ 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:" + /* hierPrefix + "/" + */ i.getName(); + String leafLookup = "INST:" + i.toString(); leaf.setData(1, 0, leafLookup); objectLookup.put(leafLookup, leaf); } @@ -134,7 +135,6 @@ public QTreeWidgetItem populateCellInst(QTreeWidgetItem curr, EDIFHierCellInst i HierCellInstTreeWidgetItem cellInst = new HierCellInstTreeWidgetItem(curr); cellInst.setText(0, i.getInst().getName() + " (" + i.getCellName() + ")"); cellInst.setInst(i); - cellInst.setData(0, 0, i); String instLookup = "INST:" + i.toString(); cellInst.setData(1, 0, instLookup); objectLookup.put(instLookup, cellInst); diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index ec2c3927d..493e39ede 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -74,6 +74,7 @@ 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; @@ -196,9 +197,11 @@ public void renderSchematic() { for (ElkNode topPort : elkRoot.getChildren()) { EDIFPortInst portInst = elkNodeTopPortMap.get(topPort); if (portInst != null) { + EDIFHierPortInst hierPortInst = currCellInst.getPortInst(portInst.toString()); + QPolygonF portShape = createPortShape(topPort, portInst.isOutput()); QGraphicsPolygonItem port = addPolygon(portShape, PORT_PEN, PORT_BRUSH); - String lookup = "PORT:" + portInst.getName(); + String lookup = "PORT:" + hierPortInst.toString(); port.setData(0, lookup); port.setToolTip(portInst.getName() + (portInst.isOutput() ? "(Output)" : "(Input)")); port.setAcceptsHoverEvents(true); @@ -265,7 +268,7 @@ private void renderNode(ElkNode parent, double xOffset, double yOffset) { } createHierButton(child, isExpanded, expandedCellName, xOffset, yOffset); } - String instLookup = "INST:" + relCellInstName; + String instLookup = "INST:" + child.getIdentifier(); rect.setData(0, instLookup); lookupMap.computeIfAbsent(instLookup, l -> new ArrayList<>()).add(rect); @@ -293,12 +296,12 @@ private void renderNode(ElkNode parent, double xOffset, double yOffset) { 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); + drawPin(child, port, yPort, side, isExpanded, xOffset, child.getIdentifier()); QGraphicsSimpleTextItem pinLabel = addSimpleText(port.getIdentifier()); pinLabel.setBrush(BLACK_BRUSH); pinLabel.setFont(FONT); - pinLabel.setZValue(6); + pinLabel.setZValue(2); double textWidth = pinLabel.boundingRect().width(); double textHeight = pinLabel.boundingRect().height(); double labelX = x; @@ -320,17 +323,21 @@ private void renderNode(ElkNode parent, double xOffset, double yOffset) { } } - private void drawPin(ElkNode cell, ElkPort port, double y, PortSide side, boolean isExpanded, double xOffset) { + 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(2); - + 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); @@ -434,6 +441,7 @@ private void drawSegment(double lastX, double lastY, double endX, double endY, S 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); } @@ -508,10 +516,10 @@ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, Stri ElkNode elkInst = f.createElkNode(); elkInst.setParent(parent); parent.getChildren().add(elkInst); - elkInst.setIdentifier(prefix + inst.getName()); + EDIFHierCellInst childInst = cellInst.getChild(inst); + elkInst.setIdentifier(childInst.toString()); elkInst.setProperty(CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_ORDER); instNodeMap.put(inst, elkInst); - EDIFHierCellInst childInst = cellInst.getChild(inst); elkNodeCellMap.put(elkInst, childInst); boolean isHierCell = !inst.getCellType().isLeafCellOrBlackBox(); @@ -592,7 +600,7 @@ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, Stri ElkEdge edge = ElkGraphFactory.eINSTANCE.createElkEdge(); edge.setContainingNode(parent); - edge.setIdentifier(prefix + net.getName()); + edge.setIdentifier(cellInst.toString() + "/" + net.getName()); edge.getSources().add(driver); edge.getTargets().add(sink); parent.getContainedEdges().add(edge); From fd7ca868dced72e105ed9cb5fe00d980ba29bfce Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 15:15:19 -0700 Subject: [PATCH 14/32] Refactor to use all EDIFHier classes Signed-off-by: Chris Lavin --- .../rapidwright/gui/SchematicScene.java | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 493e39ede..06f46ca5d 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -88,8 +88,8 @@ public class SchematicScene extends QGraphicsScene { private ElkNode elkRoot; - private Map portInstMap = new HashMap<>(); - private Map elkNodeTopPortMap = new HashMap<>(); + 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<>(); @@ -195,25 +195,24 @@ public void renderSchematic() { // We create arrow-shaped top port ElkNodes to serve as targets for top ports for (ElkNode topPort : elkRoot.getChildren()) { - EDIFPortInst portInst = elkNodeTopPortMap.get(topPort); - if (portInst != null) { - EDIFHierPortInst hierPortInst = currCellInst.getPortInst(portInst.toString()); - - QPolygonF portShape = createPortShape(topPort, portInst.isOutput()); + 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); - port.setToolTip(portInst.getName() + (portInst.isOutput() ? "(Output)" : "(Input)")); + 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(portInst.getName()); + 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 (portInst.isOutput()) { + if (hierPortInst.isOutput()) { // Position label to the right of top ports labelX += TOP_PORT_WIDTH + PORT_LABEL_SPACING; } else { @@ -391,8 +390,8 @@ private void renderEdges(ElkNode parent, double xOffset, double yOffset) { if (!e.getSources().isEmpty()) { ElkPort srcPort = (ElkPort) e.getSources().get(0); ElkNode portParent = (ElkNode) srcPort.getParent(); - EDIFPortInst portInst = elkNodeTopPortMap.get(portParent); - if (portInst != null && portInst.isTopLevelPort()) { + EDIFHierPortInst portInst = elkNodeTopPortMap.get(portParent); + if (portInst != null && portInst.getPortInst().isTopLevelPort()) { QPointF topPortLoc = getTopPortConnectionPoint(portParent, portInst.isOutput()); startX = topPortLoc.x(); startY = topPortLoc.y(); @@ -402,8 +401,8 @@ private void renderEdges(ElkNode parent, double xOffset, double yOffset) { if (!e.getTargets().isEmpty()) { ElkPort snkPort = (ElkPort) e.getTargets().get(0); ElkNode portParent = (ElkNode) snkPort.getParent(); - EDIFPortInst portInst = elkNodeTopPortMap.get(portParent); - if (portInst != null && portInst.isTopLevelPort()) { + EDIFHierPortInst portInst = elkNodeTopPortMap.get(portParent); + if (portInst != null && portInst.getPortInst().isTopLevelPort()) { QPointF topPortLoc = getTopPortConnectionPoint(portParent, portInst.isOutput()); endX = topPortLoc.x(); endY = topPortLoc.y(); @@ -491,7 +490,8 @@ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, Stri String portInstName = topPort.getPortInstNameFromPort(i); ElkNode elkTopPortNode = f.createElkNode(); EDIFPortInst portInst = topPort.getInternalPortInstFromIndex(i); - elkNodeTopPortMap.put(elkTopPortNode, portInst); + EDIFHierPortInst hierPortInst = cellInst.getPortInst(portInst.getName()); + elkNodeTopPortMap.put(elkTopPortNode, hierPortInst); elkTopPortNode.setDimensions(TOP_PORT_WIDTH + POINT_DIST, TOP_PORT_HEIGHT); elkTopPortNode.setIdentifier(portInstName); elkTopPortNode.setParent(parent); @@ -505,13 +505,13 @@ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, Stri 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(portInst, elkTopPort); + portInstMap.put(cellInst.getPortInst(portInstName), elkTopPort); elkTopPortNode.getPorts().add(elkTopPort); } } } - Map instNodeMap = new HashMap<>(); + Map instNodeMap = new HashMap<>(); for (EDIFCellInst inst : cell.getCellInsts()) { ElkNode elkInst = f.createElkNode(); elkInst.setParent(parent); @@ -519,7 +519,7 @@ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, Stri EDIFHierCellInst childInst = cellInst.getChild(inst); elkInst.setIdentifier(childInst.toString()); elkInst.setProperty(CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_ORDER); - instNodeMap.put(inst, elkInst); + instNodeMap.put(childInst, elkInst); elkNodeCellMap.put(elkInst, childInst); boolean isHierCell = !inst.getCellType().isLeafCellOrBlackBox(); @@ -530,20 +530,19 @@ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, Stri labelElkNode(elkInst, inst.getCellName()); // Create ports - Map westPorts = new TreeMap<>(); - Map eastPorts = new TreeMap<>(); + Map westPorts = new TreeMap<>(); + Map eastPorts = new TreeMap<>(); double longestWestName = 0; double longestEastName = 0; - for (EDIFPort port : inst.getCellPorts()) { - for (int i : port.isBus() ? port.getBitBlastedIndicies() : new int[] { 0 }) { - String name = port.getPortInstNameFromPort(i); - if (port.isInput()) { - westPorts.put(name, inst.getPortInst(name)); - longestWestName = Math.max(longestWestName, fm.width(name)); - } else { - eastPorts.put(name, inst.getPortInst(name)); - longestEastName = Math.max(longestEastName, fm.width(name)); - } + // 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)); } } @@ -566,34 +565,35 @@ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, Stri fm.height() + LABEL_BUFFER * 2, // Bottom SIDE_PADDING // Side )); - createExpandedCellInnerPorts(inst); + createExpandedCellInnerPorts(childInst); populateCellContent(childInst, elkInst, prefix + inst.getName() + "/"); } } for (EDIFNet net : cell.getNets()) { - List drivers = new ArrayList<>(); - List sinks = new ArrayList<>(); + 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(p); + sinks.add(hierPortInst); } else { - drivers.add(p); + drivers.add(hierPortInst); } } else { if (p.isOutput()) { - drivers.add(p); + drivers.add(hierPortInst); } else { - sinks.add(p); + sinks.add(hierPortInst); } } } - for (EDIFPortInst d : drivers) { + for (EDIFHierPortInst d : drivers) { ElkPort driver = getOrCreateElkPort(d, prefix, instNodeMap); - for (EDIFPortInst s : sinks) { + for (EDIFHierPortInst s : sinks) { ElkPort sink = getOrCreateElkPort(s, prefix, instNodeMap); if (driver == null || sink == null) continue; @@ -609,31 +609,31 @@ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, Stri } } - private void createExpandedCellInnerPorts(EDIFCellInst inst) { - for (EDIFPort port : inst.getCellPorts()) { + private void createExpandedCellInnerPorts(EDIFHierCellInst inst) { + for (EDIFPort port : inst.getCellType().getPorts()) { for (int i : (port.isBus() ? port.getBitBlastedIndicies() : new int[] { 0 })) { - EDIFPortInst outerPortInst = inst.getPortInst(port.getPortInstNameFromPort(i)); + 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); - portInstMap.put(innerPortInst, outerElkPort); + portInstMap.put(inst.getPortInst(innerPortInst.getName()), outerElkPort); } } } } - private ElkPort getOrCreateElkPort(EDIFPortInst p, String prefix, Map instNodeMap) { + private ElkPort getOrCreateElkPort(EDIFHierPortInst p, String prefix, Map instNodeMap) { ElkPort port = portInstMap.get(p); if (port == null) { port = ElkGraphFactory.eINSTANCE.createElkPort(); - if (p.isTopLevelPort()) { + if (p.getPortInst().isTopLevelPort()) { return null; } else { - ElkNode inst = instNodeMap.get(p.getCellInst()); + ElkNode inst = instNodeMap.get(p.getFullHierarchicalInst()); port.setParent(inst); inst.getPorts().add(port); - port.setIdentifier(p.getName()); + port.setIdentifier(p.getPortInst().getName()); port.setDimensions(PORT_SIZE, PORT_SIZE); port.setProperty(CoreOptions.PORT_SIDE, p.isOutput() ? PortSide.EAST : PortSide.WEST); } @@ -643,9 +643,9 @@ private ElkPort getOrCreateElkPort(EDIFPortInst p, String prefix, Map portNames, + private int createElkPorts(int startIdx, boolean isHierCell, ElkNode parent, Map portNames, PortSide side) { - for (Entry e : portNames.entrySet()) { + for (Entry e : portNames.entrySet()) { ElkPort port = ElkGraphFactory.eINSTANCE.createElkPort(); portInstMap.put(e.getValue(), port); port.setParent(parent); From 097d82a774c7276b87be638f64bd8dc25c7a3188 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 16:06:38 -0700 Subject: [PATCH 15/32] Fix disconnected ports Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/gui/SchematicScene.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 06f46ca5d..7b6440b48 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -592,9 +592,9 @@ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, Stri } for (EDIFHierPortInst d : drivers) { - ElkPort driver = getOrCreateElkPort(d, prefix, instNodeMap); + ElkPort driver = getOrCreateElkPort(d, prefix, instNodeMap, cellInst); for (EDIFHierPortInst s : sinks) { - ElkPort sink = getOrCreateElkPort(s, prefix, instNodeMap); + ElkPort sink = getOrCreateElkPort(s, prefix, instNodeMap, cellInst); if (driver == null || sink == null) continue; @@ -623,12 +623,14 @@ private void createExpandedCellInnerPorts(EDIFHierCellInst inst) { } } - private ElkPort getOrCreateElkPort(EDIFHierPortInst p, String prefix, Map instNodeMap) { + 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()) { - return null; + p = cellInst.getPortInst(p.getPortInst().getName()); + port = portInstMap.get(p); } else { ElkNode inst = instNodeMap.get(p.getFullHierarchicalInst()); port.setParent(inst); From 9bd14eb84d22067bcf2f5ef68163c0d23eaba692 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 16:49:29 -0700 Subject: [PATCH 16/32] Fix NPE Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/gui/SchematicScene.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 7b6440b48..be3393e56 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -447,7 +447,7 @@ private void drawSegment(double lastX, double lastY, double endX, double endY, S 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); + 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) { @@ -489,8 +489,7 @@ private void populateCellContent(EDIFHierCellInst cellInst, ElkNode parent, Stri for (int i : (topPort.isBus() ? topPort.getBitBlastedIndicies() : new int[] { 0 })) { String portInstName = topPort.getPortInstNameFromPort(i); ElkNode elkTopPortNode = f.createElkNode(); - EDIFPortInst portInst = topPort.getInternalPortInstFromIndex(i); - EDIFHierPortInst hierPortInst = cellInst.getPortInst(portInst.getName()); + EDIFHierPortInst hierPortInst = cellInst.getPortInst(portInstName); elkNodeTopPortMap.put(elkTopPortNode, hierPortInst); elkTopPortNode.setDimensions(TOP_PORT_WIDTH + POINT_DIST, TOP_PORT_HEIGHT); elkTopPortNode.setIdentifier(portInstName); From 7b795688d4ef81efe95416968fea0d89f9425ba4 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 18:11:38 -0700 Subject: [PATCH 17/32] Fix multi-level hierarchy issue. Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/gui/SchematicScene.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index be3393e56..9fa6a7c8d 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -225,11 +225,11 @@ public void renderSchematic() { } } - renderNode(elkRoot, 0, 0); + renderNode(elkRoot, 0, 0, ""); renderEdges(elkRoot, 0, 0); } - private void renderNode(ElkNode parent, double xOffset, double yOffset) { + 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)) { @@ -244,7 +244,7 @@ private void renderNode(ElkNode parent, double xOffset, double yOffset) { EDIFCellInst eci = cell.getCellInst(relCellInstName); boolean isLeaf = true; - String cellInstName = elkRoot.equals(parent) ? (eci != null ? eci.getName() : "") : child.getIdentifier(); + String cellInstName = prefix + relCellInstName; boolean isExpanded = expandedCellInsts.contains(cellInstName); if (child.getChildren().size() > 0) { isLeaf = false; @@ -259,13 +259,12 @@ private void renderNode(ElkNode parent, double xOffset, double yOffset) { if (isLeaf) { rect = addRect(x, y, child.getWidth(), child.getHeight(), CELL_PEN, CELL_BRUSH); } else { - String expandedCellName = !elkRoot.equals(parent) ? child.getIdentifier() : eci.getName(); if (isExpanded) { rect = addRect(x, y, child.getWidth(), child.getHeight(), EXPANDED_HIER_CELL_PEN, EXPANDED_HIER_CELL_BRUSH); } else { rect = addRect(x, y, child.getWidth(), child.getHeight(), HIER_CELL_PEN, HIER_CELL_BRUSH); } - createHierButton(child, isExpanded, expandedCellName, xOffset, yOffset); + createHierButton(child, isExpanded, cellInstName, xOffset, yOffset); } String instLookup = "INST:" + child.getIdentifier(); rect.setData(0, instLookup); @@ -317,7 +316,7 @@ private void renderNode(ElkNode parent, double xOffset, double yOffset) { } if (child.getChildren().size() > 0) { - renderNode(child, x, y); + renderNode(child, x, y, prefix + relCellInstName + "/"); } } } From bbf12884ee8a047be4ce99ff54043e335fbd38cc Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 19:32:43 -0700 Subject: [PATCH 18/32] Fixing Z layers Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/gui/SchematicScene.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 9fa6a7c8d..97549285b 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -103,6 +103,7 @@ public class SchematicScene extends QGraphicsScene { 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); @@ -256,18 +257,23 @@ private void renderNode(ElkNode parent, double xOffset, double yOffset, String p 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(); - rect.setData(0, instLookup); + clickRect.setData(0, instLookup); lookupMap.computeIfAbsent(instLookup, l -> new ArrayList<>()).add(rect); ElkLabel instNameLabel = child.getLabels().get(0); // instance name @@ -299,7 +305,7 @@ private void renderNode(ElkNode parent, double xOffset, double yOffset, String p QGraphicsSimpleTextItem pinLabel = addSimpleText(port.getIdentifier()); pinLabel.setBrush(BLACK_BRUSH); pinLabel.setFont(FONT); - pinLabel.setZValue(2); + pinLabel.setZValue(5); double textWidth = pinLabel.boundingRect().width(); double textHeight = pinLabel.boundingRect().height(); double labelX = x; From ac67ac082510c85c1ca46d5edf18a5f08952df2e Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 21:48:52 -0700 Subject: [PATCH 19/32] Update for ELK and Guava license inclusion. Signed-off-by: Chris Lavin --- LICENSE.TXT | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 272 insertions(+), 1 deletion(-) diff --git a/LICENSE.TXT b/LICENSE.TXT index 62ea37d39..88affb0f3 100755 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -104,6 +104,9 @@ RapidWright (Apache 2.0) - Apache Commons IO 2.11.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) + @@ -3234,7 +3237,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 @@ -9069,4 +9072,272 @@ 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 ********************************* From e1995b86ad7dc6a368cfd6fae739e4f8acc588c5 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 21:55:55 -0700 Subject: [PATCH 20/32] Cleanup Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/gui/NetlistBrowser.java | 1 - src/com/xilinx/rapidwright/gui/SchematicView.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java index 29033d3f1..41beb14eb 100644 --- a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java +++ b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java @@ -123,7 +123,6 @@ public void selectNetlistItem(QModelIndex index) { if (item instanceof HierCellInstTreeWidgetItem) { EDIFHierCellInst cellInst = ((HierCellInstTreeWidgetItem) item).getInst(); schematicScene.drawCell(cellInst); - // TODO We need to check the current parent to see if that we can select in the schematic } else if (item.data(0, 0) instanceof EDIFPort) { EDIFPort port = (EDIFPort) item.data(0, 0); schematicScene.selectObject(item.data(1, 0).toString(), true); diff --git a/src/com/xilinx/rapidwright/gui/SchematicView.java b/src/com/xilinx/rapidwright/gui/SchematicView.java index 75238ba28..a50ffe5f4 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicView.java +++ b/src/com/xilinx/rapidwright/gui/SchematicView.java @@ -159,4 +159,4 @@ public void zoomOut() { if (this.matrix().m11() > zoomMin) scale(1.0 / scaleFactor, 1.0 / scaleFactor); } -} \ No newline at end of file +} From b5f84d1deef7d6be0d3665e9a12e9c0aa2e81b89 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 22:22:58 -0700 Subject: [PATCH 21/32] Temporarily remove cache Signed-off-by: Chris Lavin --- .github/workflows/test-rapidwright-wrapper.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-rapidwright-wrapper.yml b/.github/workflows/test-rapidwright-wrapper.yml index a9b79aa39..1dc6d1404 100644 --- a/.github/workflows/test-rapidwright-wrapper.yml +++ b/.github/workflows/test-rapidwright-wrapper.yml @@ -18,7 +18,7 @@ jobs: with: distribution: 'temurin' java-version: '11' - cache: 'gradle' + - name: Compile run: ./gradlew compileJava From 58e894cc7ad09dae76023b156ec6cc5a8c808897 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 22:37:56 -0700 Subject: [PATCH 22/32] Restore cache entry Signed-off-by: Chris Lavin --- .github/workflows/test-rapidwright-wrapper.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-rapidwright-wrapper.yml b/.github/workflows/test-rapidwright-wrapper.yml index 1dc6d1404..a9b79aa39 100644 --- a/.github/workflows/test-rapidwright-wrapper.yml +++ b/.github/workflows/test-rapidwright-wrapper.yml @@ -18,7 +18,7 @@ jobs: with: distribution: 'temurin' java-version: '11' - + cache: 'gradle' - name: Compile run: ./gradlew compileJava From 899fbd82177ea87d3ed7fb46020a65a0fd9f6a92 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 22:42:48 -0700 Subject: [PATCH 23/32] Update Jar test Signed-off-by: Chris Lavin --- .github/workflows/test-rapidwright-wrapper.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test-rapidwright-wrapper.yml b/.github/workflows/test-rapidwright-wrapper.yml index a9b79aa39..88c35a1de 100644 --- a/.github/workflows/test-rapidwright-wrapper.yml +++ b/.github/workflows/test-rapidwright-wrapper.yml @@ -20,6 +20,9 @@ jobs: java-version: '11' cache: 'gradle' + - name: Update Jars + run: ./gradlew updateJars + - name: Compile run: ./gradlew compileJava From 1e96ef63a425128348763c9a2fdda97635cfad62 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 22:45:24 -0700 Subject: [PATCH 24/32] List jars Signed-off-by: Chris Lavin --- .github/workflows/test-rapidwright-wrapper.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test-rapidwright-wrapper.yml b/.github/workflows/test-rapidwright-wrapper.yml index 88c35a1de..62baf629e 100644 --- a/.github/workflows/test-rapidwright-wrapper.yml +++ b/.github/workflows/test-rapidwright-wrapper.yml @@ -23,6 +23,9 @@ jobs: - name: Update Jars run: ./gradlew updateJars + - name: List Jars + run: ls -l jars + - name: Compile run: ./gradlew compileJava From 5b33a71ad0e2703914c20c5df5f11dbc0da7d2bf Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Wed, 12 Nov 2025 07:53:25 -0700 Subject: [PATCH 25/32] Rename cache Signed-off-by: Chris Lavin --- .github/workflows/test-rapidwright-wrapper.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/test-rapidwright-wrapper.yml b/.github/workflows/test-rapidwright-wrapper.yml index 62baf629e..6c6016acb 100644 --- a/.github/workflows/test-rapidwright-wrapper.yml +++ b/.github/workflows/test-rapidwright-wrapper.yml @@ -18,13 +18,7 @@ jobs: with: distribution: 'temurin' java-version: '11' - cache: 'gradle' - - - name: Update Jars - run: ./gradlew updateJars - - - name: List Jars - run: ls -l jars + cache: 'gradle-2' - name: Compile run: ./gradlew compileJava From 107791b6f0192ad688d3048750d717303ec8fb28 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Wed, 12 Nov 2025 07:55:18 -0700 Subject: [PATCH 26/32] Try new gradle home dir Signed-off-by: Chris Lavin --- .github/workflows/test-rapidwright-wrapper.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-rapidwright-wrapper.yml b/.github/workflows/test-rapidwright-wrapper.yml index 6c6016acb..7008b56f8 100644 --- a/.github/workflows/test-rapidwright-wrapper.yml +++ b/.github/workflows/test-rapidwright-wrapper.yml @@ -18,10 +18,10 @@ jobs: with: distribution: 'temurin' java-version: '11' - cache: 'gradle-2' + cache: 'gradle' - name: Compile - run: ./gradlew compileJava + run: GRADLE_USER_HOME=gradle_home ./gradlew compileJava - name: Test RapidWright Wrapper run: bin/rapidwright From 2c695c44ccd6ebf8f132cca3bb72492b072cafe8 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Wed, 12 Nov 2025 08:00:40 -0700 Subject: [PATCH 27/32] More debug Signed-off-by: Chris Lavin --- .github/workflows/test-rapidwright-wrapper.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-rapidwright-wrapper.yml b/.github/workflows/test-rapidwright-wrapper.yml index 7008b56f8..a18188b68 100644 --- a/.github/workflows/test-rapidwright-wrapper.yml +++ b/.github/workflows/test-rapidwright-wrapper.yml @@ -4,6 +4,9 @@ on: push: pull_request: +env: + GRADLE_USER_HOME: gradle_home + jobs: build: strategy: @@ -13,15 +16,15 @@ 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 - run: GRADLE_USER_HOME=gradle_home ./gradlew compileJava + run: ./gradlew compileJava - name: Test RapidWright Wrapper run: bin/rapidwright From 8ba6fb3d3b28d6277a0bec42521c5b0a0c82e7ee Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Wed, 12 Nov 2025 09:07:51 -0700 Subject: [PATCH 28/32] Remove env Signed-off-by: Chris Lavin --- .github/workflows/test-rapidwright-wrapper.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test-rapidwright-wrapper.yml b/.github/workflows/test-rapidwright-wrapper.yml index a18188b68..91e97777a 100644 --- a/.github/workflows/test-rapidwright-wrapper.yml +++ b/.github/workflows/test-rapidwright-wrapper.yml @@ -4,9 +4,6 @@ on: push: pull_request: -env: - GRADLE_USER_HOME: gradle_home - jobs: build: strategy: From fd67f260abbb152b9ad8bf029d1354735fb8f41f Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Wed, 12 Nov 2025 15:58:11 -0700 Subject: [PATCH 29/32] Fix NPE on inner port Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/gui/SchematicScene.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 97549285b..78ec5ed5d 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -621,7 +621,9 @@ private void createExpandedCellInnerPorts(EDIFHierCellInst inst) { // Map the inner port inst to the outer one so nets are aligned if (outerElkPort != null) { EDIFPortInst innerPortInst = port.getInternalPortInstFromIndex(i); - portInstMap.put(inst.getPortInst(innerPortInst.getName()), outerElkPort); + if (innerPortInst != null) { + portInstMap.put(inst.getPortInst(innerPortInst.getName()), outerElkPort); + } } } } From 80db61c8045b106fec1e09fd70c7e53792840364 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Wed, 12 Nov 2025 16:56:39 -0700 Subject: [PATCH 30/32] Enable top selection Signed-off-by: Chris Lavin --- .../xilinx/rapidwright/gui/HierCellInstTreeWidgetItem.java | 4 ++++ src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java | 3 ++- src/com/xilinx/rapidwright/gui/SchematicScene.java | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) 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/NetlistTreeWidget.java b/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java index 190240f71..8995db00a 100644 --- a/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java +++ b/src/com/xilinx/rapidwright/gui/NetlistTreeWidget.java @@ -57,7 +57,8 @@ 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()); diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 78ec5ed5d..60ef3c822 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -125,7 +125,7 @@ public class SchematicScene extends QGraphicsScene { 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 = 20.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; @@ -238,7 +238,7 @@ private void renderNode(ElkNode parent, double xOffset, double yOffset, String p continue; } String relCellInstName = child.getIdentifier(); - if (relCellInstName.startsWith(parent.getIdentifier())) { + if (relCellInstName.startsWith(parent.getIdentifier()) && parent.getIdentifier().length() > 0) { // Remove hierarchical reference relCellInstName = relCellInstName.substring(parent.getIdentifier().length() + 1); } From 9c4f2c893de0742f889c5a41216b92604bc7e3a5 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Wed, 12 Nov 2025 19:54:52 -0700 Subject: [PATCH 31/32] Add zoom to fit when selecting a new cell. Signed-off-by: Chris Lavin --- .../rapidwright/gui/NetlistBrowser.java | 3 ++- .../rapidwright/gui/SchematicScene.java | 9 +++++++-- .../xilinx/rapidwright/gui/SchematicView.java | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java index 41beb14eb..d7d13db46 100644 --- a/src/com/xilinx/rapidwright/gui/NetlistBrowser.java +++ b/src/com/xilinx/rapidwright/gui/NetlistBrowser.java @@ -116,13 +116,14 @@ private void init() { 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); + 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); diff --git a/src/com/xilinx/rapidwright/gui/SchematicScene.java b/src/com/xilinx/rapidwright/gui/SchematicScene.java index 60ef3c822..83a0b5969 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicScene.java +++ b/src/com/xilinx/rapidwright/gui/SchematicScene.java @@ -96,6 +96,7 @@ public class SchematicScene extends QGraphicsScene { 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()); @@ -168,7 +169,7 @@ private void applyElkNodeProperties(ElkNode root) { root.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_TO_NODE_SPACING); } - public void drawCell(EDIFHierCellInst cellInst) { + public void drawCell(EDIFHierCellInst cellInst, boolean zoomFit) { clear(); portInstMap.clear(); elkNodeTopPortMap.clear(); @@ -185,6 +186,9 @@ public void drawCell(EDIFHierCellInst cellInst) { engine.layout(elkRoot, monitor); renderSchematic(); + if (zoomFit) { + cellDrawn.emit(); + } } public void renderSchematic() { @@ -700,7 +704,8 @@ private void toggleCellInstExpansion(String cellInstName) { } else { expandedCellInsts.add(cellInstName); } - drawCell(currCellInst); + boolean zoomFit = false; + drawCell(currCellInst, zoomFit); } private void toggleSelection(String lookup, boolean multipleSelection) { diff --git a/src/com/xilinx/rapidwright/gui/SchematicView.java b/src/com/xilinx/rapidwright/gui/SchematicView.java index a50ffe5f4..866a2dde8 100644 --- a/src/com/xilinx/rapidwright/gui/SchematicView.java +++ b/src/com/xilinx/rapidwright/gui/SchematicView.java @@ -24,7 +24,9 @@ import com.trolltech.qt.core.QPoint; import com.trolltech.qt.core.QPointF; +import com.trolltech.qt.core.QRectF; import com.trolltech.qt.core.Qt; +import com.trolltech.qt.core.Qt.AspectRatioMode; import com.trolltech.qt.core.Qt.CursorShape; import com.trolltech.qt.core.Qt.Key; import com.trolltech.qt.gui.QCursor; @@ -159,4 +161,21 @@ public void zoomOut() { if (this.matrix().m11() > zoomMin) scale(1.0 / scaleFactor, 1.0 / scaleFactor); } + + public void zoomToFit() { + QRectF sceneRect = scene().sceneRect(); + if (sceneRect != null) { + fitInView(sceneRect, AspectRatioMode.KeepAspectRatio); + double zoom = this.matrix().m11(); + if (zoom > zoomMax) { + resetMatrix(); + scale(zoomMax, zoomMax); + fitInView(sceneRect, AspectRatioMode.KeepAspectRatio); + } else if (zoom < zoomMin) { + resetMatrix(); + scale(zoomMin, zoomMin); + fitInView(sceneRect, AspectRatioMode.KeepAspectRatio); + } + } + } } From 28db4880848037c4eac0487538686c4ab602551d Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 13 Nov 2025 10:21:20 -0700 Subject: [PATCH 32/32] Workaround to support JDK8 execution with ELK Signed-off-by: Chris Lavin --- common.gradle | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/common.gradle b/common.gradle index d10db3ea3..90fa8094f 100644 --- a/common.gradle +++ b/common.gradle @@ -48,9 +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' - 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' + 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'