From f10322e8a04d8f4919092d035bb7f1b4bdd7ee43 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 10 Apr 2025 09:57:55 -0700 Subject: [PATCH 01/24] Merge master Signed-off-by: Chris Lavin --- .classpath | 4 +- .github/workflows/build.yml | 2 +- RELEASE_NOTES.TXT | 18 ++ python/setup.py | 2 +- python/src/rapidwright/rapidwright.py | 2 +- .../xilinx/rapidwright/MainEntrypoint.java | 2 + .../rapidwright/design/DesignTools.java | 238 +++++++++------- .../rapidwright/design/RTLStubGenerator.java | 4 +- .../xilinx/rapidwright/edif/EDIFNetlist.java | 116 ++++++-- src/com/xilinx/rapidwright/edif/EDIFPort.java | 4 +- .../rapidwright/examples/CountRoutedNets.java | 81 ++++++ .../xilinx/rapidwright/ipi/BlockStitcher.java | 36 +-- .../rapidwright/util/CodeGenerator.java | 258 ++++++++++++++++++ .../rapidwright/design/TestDesignTools.java | 24 ++ .../xilinx/rapidwright/eco/TestECOTools.java | 34 +++ .../rapidwright/edif/TestEDIFNetlist.java | 23 +- .../xilinx/rapidwright/edif/TestEDIFPort.java | 25 ++ .../rapidwright/util/TestCodeGenerator.java | 80 ++++++ 18 files changed, 809 insertions(+), 144 deletions(-) create mode 100644 src/com/xilinx/rapidwright/examples/CountRoutedNets.java diff --git a/.classpath b/.classpath index 154addfe2..896347adc 100644 --- a/.classpath +++ b/.classpath @@ -33,9 +33,9 @@ - + - + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac3e2074c..3bbbd5f77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: pull_request: env: - RAPIDWRIGHT_VERSION: v2024.2.1-beta + RAPIDWRIGHT_VERSION: v2024.2.2-beta jobs: build: diff --git a/RELEASE_NOTES.TXT b/RELEASE_NOTES.TXT index 4206b5730..ee081678b 100644 --- a/RELEASE_NOTES.TXT +++ b/RELEASE_NOTES.TXT @@ -1,3 +1,21 @@ +============= RapidWright 2024.2.2-beta released on 2025-3-25 ================ +Notes: + - [DesignTools] Fix routethru corner case in fullyUnplaceCellHelper() (#1157) + - Add option to uniquify copied cells (#1156) + - [DesignTools] Ensure cell pin has a logical connection before attempting to unroute it (#1154) + - [DesignTools] Unroutes both CLK pins on a BRAM when requested (#1151) + - [EDIF] Fix Export of Single Bit Busses (#1147) + - Fixes to enable BlockStitcher to run (#1134) + - Enables batching of SitePinInst removals during MakeBlackBox (#1142) + - Routing Heat Map Example (#1145) + - When adding Modules to Designs, make EDIF cell names unique if name collides + +API Additions: + - com.xilinx.rapidwright.design.Design "public boolean removeSiteInst(SiteInst instance, boolean keepSitePinRouting, + - com.xilinx.rapidwright.design.Design "public Map> getCompatiblePlacements(Unisim u)" + - com.xilinx.rapidwright.design.Design "public static Map> getCompatiblePlacements(FamilyType family, Unisim u)" + + ============= RapidWright 2024.2.1-beta released on 2025-1-15 ================ Notes: - Adds an Override Flag for Advanced Flow Settings in Designs (#1135) diff --git a/python/setup.py b/python/setup.py index b8b4345ac..666706481 100644 --- a/python/setup.py +++ b/python/setup.py @@ -24,7 +24,7 @@ setup( name='rapidwright', - version='2024.2.1', + version='2024.2.2', license='Apache 2.0 and Others', description='Xilinx RapidWright Framework Wrapped for Python.', long_description='', diff --git a/python/src/rapidwright/rapidwright.py b/python/src/rapidwright/rapidwright.py index 24eda017f..76279a2d0 100644 --- a/python/src/rapidwright/rapidwright.py +++ b/python/src/rapidwright/rapidwright.py @@ -24,7 +24,7 @@ from typing import List, Optional import os, urllib.request, platform -version='2024.2.1' +version='2024.2.2' def start_jvm(): os_str = 'lin64' diff --git a/src/com/xilinx/rapidwright/MainEntrypoint.java b/src/com/xilinx/rapidwright/MainEntrypoint.java index d56fde89f..ee8b519bc 100644 --- a/src/com/xilinx/rapidwright/MainEntrypoint.java +++ b/src/com/xilinx/rapidwright/MainEntrypoint.java @@ -50,6 +50,7 @@ import com.xilinx.rapidwright.edif.EDIFTools; import com.xilinx.rapidwright.examples.AddSubGenerator; import com.xilinx.rapidwright.examples.CopyMMCMCell; +import com.xilinx.rapidwright.examples.CountRoutedNets; import com.xilinx.rapidwright.examples.CustomRouting; import com.xilinx.rapidwright.examples.DecomposeLUT; import com.xilinx.rapidwright.examples.ExampleNetlistCreation; @@ -131,6 +132,7 @@ private static void addFunction(String name, MainStyleFunction func) { addFunction("CheckAccuracyUsingGnlDesigns", CheckAccuracyUsingGnlDesigns::main); addFunction("CompareRouteStatusReports", CompareRouteStatusReports::main); addFunction("CopyMMCMCell", CopyMMCMCell::main); + addFunction("CountRoutedNets", CountRoutedNets::main); addFunction("CUFR", CUFR::main); addFunction("CustomRouting", CustomRouting::main); addFunction("DcpToInterchange", DcpToInterchange::main); diff --git a/src/com/xilinx/rapidwright/design/DesignTools.java b/src/com/xilinx/rapidwright/design/DesignTools.java index f4db21143..021b05af8 100644 --- a/src/com/xilinx/rapidwright/design/DesignTools.java +++ b/src/com/xilinx/rapidwright/design/DesignTools.java @@ -1081,14 +1081,15 @@ public static boolean removeConnectedRouting(Net net, Node node) { */ public static void unroutePins(Net net, Collection pins) { List sinkPins = new ArrayList<>(pins.size()); - pins.forEach((spi) -> { - if (spi.isOutPin()) { - // TODO - This can lead to a slow down in VCC and GND nets as it is not batched - DesignTools.unrouteSourcePin(spi); + List srcPins = new ArrayList<>(); + for (SitePinInst pin : pins) { + if (pin.isOutPin()) { + srcPins.add(pin); } else { - sinkPins.add(spi); + sinkPins.add(pin); } - }); + } + DesignTools.unrouteSourcePins(srcPins); removePIPsFromNet(net,getTrimmablePIPsFromPins(net, sinkPins)); for (SitePinInst pin : sinkPins) { pin.setRouted(false); @@ -1113,41 +1114,62 @@ private static void removePIPsFromNet(Net net, Set pipsToRemove) { * @return The set of PIPs that were unrouted from the net. */ public static Set unrouteSourcePin(SitePinInst src) { - if (!src.isOutPin() || src.getNet() == null) return Collections.emptySet(); - Node srcNode = src.getConnectedNode(); - Set pipsToRemove = new HashSet<>(); + return unrouteSourcePins(Collections.singletonList(src)); + } + /** + * Unroutes a list of source SitePinInst of a net. This is desirable when a net + * has multiple SitePinInst source pins (multiple outputs of a Site) and only a + * particular branch is desired to be unrouted. If the entire net is to be + * unrouted, a more efficient method is {@link Net#unroute()}. + * + * @param srcs The list of source pins of the net from which to remove the + * routing + * @return The set of PIPs that were unrouted from the net. + */ + public static Set unrouteSourcePins(List srcs) { + if (srcs == null || srcs.size() == 0) { + return Collections.emptySet(); + } + Net net = srcs.get(0).getNet(); + if (net == null) { + return Collections.emptySet(); + } Map> pipMap = new HashMap<>(); - for (PIP pip : src.getNet().getPIPs()) { + for (PIP pip : net.getPIPs()) { Node node = pip.isReversed() ? pip.getEndNode() : pip.getStartNode(); pipMap.computeIfAbsent(node, k -> new ArrayList<>()).add(pip); } Map sinkNodes = new HashMap<>(); - for (SitePinInst sinkPin : src.getNet().getSinkPins()) { + for (SitePinInst sinkPin : net.getSinkPins()) { sinkNodes.put(sinkPin.getConnectedNode(), sinkPin); } - Queue q = new LinkedList<>(); - q.add(srcNode); - while (!q.isEmpty()) { - Node curr = q.poll(); - List pips = pipMap.get(curr); - if (pips != null) { - for (PIP p : pips) { - Node endNode = p.isReversed() ? p.getStartNode() : p.getEndNode(); - q.add(endNode); - pipsToRemove.add(p); - SitePinInst sink = sinkNodes.get(endNode); - if (sink != null) { - sink.setRouted(false); + Set pipsToRemove = new HashSet<>(); + for (SitePinInst src : srcs) { + if (!src.isOutPin()) continue; + Queue q = new LinkedList<>(); + q.add(src.getConnectedNode()); + while (!q.isEmpty()) { + Node curr = q.poll(); + List pips = pipMap.get(curr); + if (pips != null) { + for (PIP p : pips) { + Node endNode = p.isReversed() ? p.getStartNode() : p.getEndNode(); + q.add(endNode); + pipsToRemove.add(p); + SitePinInst sink = sinkNodes.get(endNode); + if (sink != null) { + sink.setRouted(false); + } } } } - } - src.setRouted(false); - removePIPsFromNet(src.getNet(), pipsToRemove); + src.setRouted(false); + removePIPsFromNet(src.getNet(), pipsToRemove); + } return pipsToRemove; } @@ -1300,8 +1322,13 @@ private static void fullyUnplaceCellHelper(Cell cell, Map> Cell otherCell = siteInst.getCell(otherPin.getBEL()); if (otherCell == null) continue; if (otherCell.isRoutethru()) { - // This will be handled outside of the loop in SiteInst.unrouteIntraSiteNet() - continue; + String otherCellType = otherCell.getType(); + // Ensure the routethru cell is servicing this cell's connection + if (otherCellType.equals(Cell.FF_ROUTETHRU_TYPE) || (otherCellType.equals(cell.getType()) + && pin.getName().equals(otherCell.getFirstPhysicalPinMapping().getFirst().getName()))) { + // This will be handled outside of the loop in SiteInst.unrouteIntraSiteNet() + continue; + } } String logicalPinName = otherCell.getLogicalPinMapping(otherPin.getName()); if (logicalPinName == null) continue; @@ -1520,38 +1547,51 @@ public static void makeBlackBox(Design d, String hierarchicalCellName) { * @returns A list of site pins (if any) that should also be removed from inter-site routing to complete the unroute. */ public static List unrouteCellPinSiteRouting(Cell cell, String logicalPinName) { - String physPinName = cell.getPhysicalPinMapping(logicalPinName); - if (physPinName == null) { - physPinName = cell.getDefaultPinMapping(logicalPinName); - } - if (physPinName == null) { - // Assume not routed - return Collections.emptyList(); + List sitePinsToRemove = new ArrayList<>(); + Set physPinNames = cell.getAllPhysicalPinMappings(logicalPinName); + if (physPinNames == null) { + physPinNames = Collections.singleton(cell.getDefaultPinMapping(logicalPinName)); } - BELPin belPin = cell.getBEL().getPin(physPinName); - SiteInst siteInst = cell.getSiteInst(); - Net net = siteInst.getNetFromSiteWire(belPin.getSiteWireName()); - if (net == null) - return Collections.emptyList(); - - List sitePinNames = new ArrayList<>(); - List internalTerminals = new ArrayList<>(); - List internalSinks = new ArrayList<>(); - Set visited = new HashSet<>(); - Queue queue = new LinkedList<>(); - queue.add(belPin); - - while (!queue.isEmpty()) { - BELPin currPin = queue.poll(); - visited.add(currPin); - BELPin unrouteSegment = null; - for (BELPin pin : siteInst.getSiteWirePins(currPin.getSiteWireIndex())) { - if (currPin == pin || visited.contains(pin)) { - visited.add(pin); - continue; + for (String physPinName : physPinNames) { + if (physPinName == null) { + physPinName = cell.getDefaultPinMapping(logicalPinName); + } + if (physPinName == null) { + // Assume not routed + return Collections.emptyList(); + } + BELPin belPin = cell.getBEL().getPin(physPinName); + SiteInst siteInst = cell.getSiteInst(); + Net net = siteInst.getNetFromSiteWire(belPin.getSiteWireName()); + if (net == null) + return Collections.emptyList(); + else { + // Check if net is connected to logical cell + EDIFCellInst cellInst = cell.getEDIFCellInst(); + EDIFPortInst portInst = cellInst != null ? cellInst.getPortInst(logicalPinName) : null; + if (portInst == null || portInst.getNet() == null) { + return Collections.emptyList(); } - // Check if it is a site pin, cell pin, sitepip or routethru - switch (pin.getBEL().getBELClass()) { + } + + List sitePinNames = new ArrayList<>(); + List internalTerminals = new ArrayList<>(); + List internalSinks = new ArrayList<>(); + Set visited = new HashSet<>(); + Queue queue = new LinkedList<>(); + queue.add(belPin); + + while (!queue.isEmpty()) { + BELPin currPin = queue.poll(); + visited.add(currPin); + BELPin unrouteSegment = null; + for (BELPin pin : siteInst.getSiteWirePins(currPin.getSiteWireIndex())) { + if (currPin == pin || visited.contains(pin)) { + visited.add(pin); + continue; + } + // Check if it is a site pin, cell pin, sitepip or routethru + switch (pin.getBEL().getBELClass()) { case PORT: { // We found a site pin, add it to solution set sitePinNames.add(pin.getName()); @@ -1594,9 +1634,8 @@ public static List unrouteCellPinSiteRouting(Cell cell, String logi } } else { // site routing terminates here or is invalid - } + } } - } else if (otherCell != cell && otherCell.getLogicalPinMapping(pin.getName()) != null) { // Don't search farther, we don't need to unroute anything else if (pin.isInput() && belPin.isInput()) { @@ -1624,49 +1663,49 @@ public static List unrouteCellPinSiteRouting(Cell cell, String logi } break; } + } + visited.add(pin); + } + if (unrouteSegment != null && unrouteSegment.isInput() && internalSinks.size() == 0) { + // Unroute this branch of the sitePIP + Net otherNet = siteInst.getNetFromSiteWire(unrouteSegment.getSiteWireName()); + siteInst.unrouteIntraSiteNet(unrouteSegment, belPin); + siteInst.routeIntraSiteNet(otherNet, unrouteSegment, unrouteSegment); } - visited.add(pin); - } - if (unrouteSegment != null && unrouteSegment.isInput() && internalSinks.size() == 0) { - // Unroute this branch of the sitePIP - Net otherNet = siteInst.getNetFromSiteWire(unrouteSegment.getSiteWireName()); - siteInst.unrouteIntraSiteNet(unrouteSegment, belPin); - siteInst.routeIntraSiteNet(otherNet, unrouteSegment, unrouteSegment); } - } - List sitePinsToRemove = new ArrayList<>(); - - // This net is routed internally to the site - for (BELPin internalTerminal : internalTerminals) { - if (internalTerminal.isOutput() && internalSinks.size() > 0) { - continue; - } - if (belPin.isOutput()) { - siteInst.unrouteIntraSiteNet(belPin, internalTerminal); - } else { - siteInst.unrouteIntraSiteNet(internalTerminal, belPin); + // This net is routed internally to the site + for (BELPin internalTerminal : internalTerminals) { + if (internalTerminal.isOutput() && internalSinks.size() > 0) { + continue; + } + if (belPin.isOutput()) { + siteInst.unrouteIntraSiteNet(belPin, internalTerminal); + } else { + siteInst.unrouteIntraSiteNet(internalTerminal, belPin); + } } - } - if (internalSinks.size() == 0) { - for (String sitePinName : sitePinNames) { - SitePinInst pin = siteInst.getSitePinInst(sitePinName); - if (pin != null) { - sitePinsToRemove.add(pin); - if (belPin.isInput()) { - siteInst.unrouteIntraSiteNet(pin.getBELPin(), belPin); + if (internalSinks.size() == 0) { + for (String sitePinName : sitePinNames) { + SitePinInst pin = siteInst.getSitePinInst(sitePinName); + if (pin != null) { + sitePinsToRemove.add(pin); + if (belPin.isInput()) { + siteInst.unrouteIntraSiteNet(pin.getBELPin(), belPin); + } else { + siteInst.unrouteIntraSiteNet(belPin, pin.getBELPin()); + } } else { - siteInst.unrouteIntraSiteNet(belPin, pin.getBELPin()); + // Vivado leaves dual output *MUX partially routed, unroute the site for this + // MUX pin + // Could also be a cell with no loads + siteInst.unrouteIntraSiteNet(belPin, siteInst.getBELPin(sitePinName, sitePinName)); } - } else { - // Vivado leaves dual output *MUX partially routed, unroute the site for this MUX pin - // Could also be a cell with no loads - siteInst.unrouteIntraSiteNet(belPin, siteInst.getBELPin(sitePinName, sitePinName)); } - } - if (internalTerminals.size() == 0 && sitePinNames.size() == 0) { - // internal site route with no loads - siteInst.unrouteIntraSiteNet(belPin, belPin); + if (internalTerminals.size() == 0 && sitePinNames.size() == 0) { + // internal site route with no loads + siteInst.unrouteIntraSiteNet(belPin, belPin); + } } } return sitePinsToRemove; @@ -1820,7 +1859,6 @@ public static void makeBlackBox(Design d, EDIFHierCellInst hierarchicalCell) { // Rename nets if source was removed Set netsToKeep = new HashSet<>(); for (Entry e : netsToUpdate.entrySet()) { - EDIFHierNet newSource = d.getNetlist().getHierNetFromName(e.getValue()); Net net = e.getKey(); if (!net.rename(e.getValue())) { throw new RuntimeException("ERROR: Failed to rename net '" + net.getName() + "'"); @@ -1840,7 +1878,7 @@ public static void makeBlackBox(Design d, EDIFHierCellInst hierarchicalCell) { } for (SiteInst siteInst : siteInstsToRemove) { - d.removeSiteInst(siteInst); + d.removeSiteInst(siteInst, false, pinsToRemove); } // Remove any stray stubs on any remaining nets @@ -4131,7 +4169,7 @@ public static void unlockRouting(Design design) { lockRouting(design, false); } - /*** + /** * Unroutes the GND net of a design and unroutes the site routing of any LUT GND * sources while leaving other site routing inputs intact. * diff --git a/src/com/xilinx/rapidwright/design/RTLStubGenerator.java b/src/com/xilinx/rapidwright/design/RTLStubGenerator.java index 22eb670ee..f61830606 100644 --- a/src/com/xilinx/rapidwright/design/RTLStubGenerator.java +++ b/src/com/xilinx/rapidwright/design/RTLStubGenerator.java @@ -79,7 +79,7 @@ public static void createVerilogStub(Design d, OutputStream out) { for (int i=0; i < ports.length; i++) { EDIFPort p = ports[i]; String dir = p.getDirection().name().toLowerCase(); - String range = p.getWidth() == 1 ? "" : "[" + p.getLeft() + ":" + p.getRight() +"]"; + String range = p.isBus() ? "[" + p.getLeft() + ":" + p.getRight() + "]" : ""; out.write((" " + dir + " " + range + p.getBusName() + ";\n").getBytes()); } out.write("endmodule\n".getBytes()); @@ -101,7 +101,7 @@ public static void createVHDLStub(Design d, OutputStream out) { EDIFPort p = ports[i]; String dir = p.getDirection().name().toLowerCase().replace("put", ""); String type = " STD_LOGIC"; - if (p.getWidth() > 1) { + if (p.isBus()) { String endian = p.getLeft() > p.getRight() ? " downto " : " to "; type = " STD_LOGIC_VECTOR ( " + p.getLeft() + endian + p.getRight() + " )"; } diff --git a/src/com/xilinx/rapidwright/edif/EDIFNetlist.java b/src/com/xilinx/rapidwright/edif/EDIFNetlist.java index 601f0f3dc..9f3631973 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFNetlist.java +++ b/src/com/xilinx/rapidwright/edif/EDIFNetlist.java @@ -481,26 +481,41 @@ public List getComments() { } /** - * Migrates all cells in the provided library - * into the standard work library. - * @param library The library with cells to be migrated to work. + * Migrates all cells in the provided library into the standard work library. + * + * @param library The library with cells to be migrated to work. + * @param renameCollisions Flag to rename cells upon name collision */ - public void migrateToWorkLibrary(String library) { + public void migrateToWorkLibrary(String library, boolean renameCollisions) { EDIFLibrary work = getWorkLibrary(); EDIFLibrary oldWork = getLibrary(library); List toRemove = new ArrayList<>(oldWork.getCells()); for (EDIFCell c : toRemove) { oldWork.removeCell(c); - work.addCell(c); + if (renameCollisions) { + work.addCellRenameDuplicates(c, "Work"); + } else { + work.addCell(c); + } } removeLibrary(library); } /** - * Migrates all libraries except HDI primitives and work to - * the work library. + * Migrates all cells in the provided library into the standard work library. + * + * @param library The library with cells to be migrated to work. */ - public void consolidateAllToWorkLibrary() { + public void migrateToWorkLibrary(String library) { + migrateToWorkLibrary(library, false); + } + + /** + * Migrates all libraries except HDI primitives and work to the work library. + * + * @param renameCollisions Flag to rename cells upon name collision + */ + public void consolidateAllToWorkLibrary(boolean renameCollisions) { List librariesToMigrate = new ArrayList<>(); for (EDIFLibrary l : getLibraries()) { if (!l.isHDIPrimitivesLibrary() && !l.isWorkLibrary()) { @@ -508,10 +523,17 @@ public void consolidateAllToWorkLibrary() { } } for (EDIFLibrary l : librariesToMigrate) { - migrateToWorkLibrary(l.getName()); + migrateToWorkLibrary(l.getName(), renameCollisions); } } + /** + * Migrates all libraries except HDI primitives and work to the work library. + */ + public void consolidateAllToWorkLibrary() { + consolidateAllToWorkLibrary(false); + } + private EDIFCell migrateCellAndSubCellsWorker(EDIFCell cell) { EDIFLibrary srcLib = cell.getLibrary(); EDIFLibrary destLib = getLibrary(srcLib.getName()); @@ -617,13 +639,39 @@ public void migrateCellAndSubCells(EDIFCell cell, boolean uniqueifyCollisions) { } } + private int nameCollisionCount = 0; + + private String getUniqueCellName(String currName, EDIFLibrary destLibTop) { + String currentCellName = currName; + while (destLibTop.containsCell(currentCellName)) { + currentCellName = currName + "_RW_UNIQ_" + Integer.toString(nameCollisionCount); + nameCollisionCount++; + } + return currentCellName; + } + + /** * This copies the cell and all of its descendants into this netlist. * @param cell The cell (and all its descendants) to copy into this netlist's libraries */ public void copyCellAndSubCells(EDIFCell cell) { Set copiedCells = new HashSet<>(); - copyCellAndSubCellsWorker(cell, copiedCells); + copyCellAndSubCellsWorker(cell, copiedCells, false); + } + + /** + * This copies the cell and all of its descendants into this netlist. + * + * @param cell The cell (and all its descendants) to copy into + * this netlist's libraries + * @param uniquifyCollisions Flag that allows the method to create a uniquely + * named copy of a cell when the destination library + * already contains a cell with the same name. + */ + public void copyCellAndSubCells(EDIFCell cell, boolean uniquifyCollisions) { + Set copiedCells = new HashSet<>(); + copyCellAndSubCellsWorker(cell, copiedCells, uniquifyCollisions); } /** @@ -633,12 +681,29 @@ public void copyCellAndSubCells(EDIFCell cell) { public EDIFLibrary copyLibraryAndSubCells(EDIFLibrary library) { Set copiedCells = new HashSet<>(); for (EDIFCell cell : library.getCells()) { - copyCellAndSubCellsWorker(cell, copiedCells); + copyCellAndSubCellsWorker(cell, copiedCells, false); + } + return getLibrary(library.getName()); + } + + /** + * This copies the library and all of its cells into this netlist. + * + * @param library The library (and all its cells) to copy into this + * netlist's libraries + * @param uniquifyCollisions Flag that allows the method to create a uniquely + * named copy of a cell when the destination library + * already contains a cell with the same name. + */ + public EDIFLibrary copyLibraryAndSubCells(EDIFLibrary library, boolean uniquifyCollisions) { + Set copiedCells = new HashSet<>(); + for (EDIFCell cell : library.getCells()) { + copyCellAndSubCellsWorker(cell, copiedCells, uniquifyCollisions); } return getLibrary(library.getName()); } - private EDIFCell copyCellAndSubCellsWorker(EDIFCell cell, Set copiedCells) { + private EDIFCell copyCellAndSubCellsWorker(EDIFCell cell, Set copiedCells, boolean uniquifyCollisions) { EDIFLibrary destLib = getLibrary(cell.getLibrary().getName()); if (destLib == null) { if (cell.getLibrary().isHDIPrimitivesLibrary()) { @@ -648,12 +713,14 @@ private EDIFCell copyCellAndSubCellsWorker(EDIFCell cell, Set copiedCe } } - EDIFCell existingCell = destLib.getCell(cell.getName()); + String cellName = cell.getName(); + EDIFCell existingCell = destLib.getCell(cellName); if (existingCell == null) { - EDIFCell newCell = new EDIFCell(destLib, cell, cell.getName()); + EDIFCell newCell = new EDIFCell(destLib, cell, cellName); copiedCells.add(newCell); for (EDIFCellInst inst : newCell.getCellInsts()) { - inst.setCellType(copyCellAndSubCellsWorker(inst.getCellType(), copiedCells)); + inst.setCellType(copyCellAndSubCellsWorker(inst.getCellType(), copiedCells, + uniquifyCollisions)); //The view might have changed inst.getViewref().setName(inst.getCellType().getView()); } @@ -661,9 +728,24 @@ private EDIFCell copyCellAndSubCellsWorker(EDIFCell cell, Set copiedCe } else { if (destLib.isHDIPrimitivesLibrary() || copiedCells.contains(existingCell) || cell == existingCell) { return existingCell; + } else if (uniquifyCollisions) { + // We need to rename the cell to a unique name + String uniqueCellName = getUniqueCellName(cell.getName(), destLib); + EDIFCell newCell = new EDIFCell(destLib, cell, uniqueCellName); + copiedCells.add(newCell); + for (EDIFCellInst inst : newCell.getCellInsts()) { + inst.setCellType(copyCellAndSubCellsWorker(inst.getCellType(), copiedCells, + uniquifyCollisions)); + // The view might have changed + inst.getViewref().setName(inst.getCellType().getView()); + } + return newCell; + + } else { + throw new RuntimeException( + "ERROR: Destination netlist already contains EDIFCell named " + "'" + + cell.getName() + "' in library '" + destLib.getName() + "'"); } - throw new RuntimeException("ERROR: Destination netlist already contains EDIFCell named " + - "'" + cell.getName() + "' in library '" + destLib.getName() + "'"); } } diff --git a/src/com/xilinx/rapidwright/edif/EDIFPort.java b/src/com/xilinx/rapidwright/edif/EDIFPort.java index 9f47427b2..d9a8c3a81 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFPort.java +++ b/src/com/xilinx/rapidwright/edif/EDIFPort.java @@ -259,14 +259,14 @@ public int getPortIndexFromNameIndex(int namedIndex) { public void exportEDIF(OutputStream os, EDIFWriteLegalNameCache cache, boolean stable) throws IOException{ os.write(EXPORT_CONST_INDENT); os.write(EXPORT_CONST_PORT_BEGIN); - if (width > 1) os.write(EXPORT_CONST_ARRAY_BEGIN); if (isBus()) { + os.write(EXPORT_CONST_ARRAY_BEGIN); exportEDIFBusName(os, cache); } else { exportEDIFName(os, cache); } - if (width > 1) { + if (isBus()) { os.write(' '); os.write(Integer.toString(width).getBytes(StandardCharsets.UTF_8)); os.write(')'); diff --git a/src/com/xilinx/rapidwright/examples/CountRoutedNets.java b/src/com/xilinx/rapidwright/examples/CountRoutedNets.java new file mode 100644 index 000000000..fe4b761c0 --- /dev/null +++ b/src/com/xilinx/rapidwright/examples/CountRoutedNets.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Coherent Ho, Synopsys, Inc. + * + * 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.examples; + +import java.util.ArrayList; +import java.util.Map; + +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.DesignTools; +import com.xilinx.rapidwright.design.Net; +import com.xilinx.rapidwright.design.SitePinInst; + + +/** + * Simple tool for get the partially routed nets in a design. + */ +public class CountRoutedNets { + + public static void main(String[] args) { + if (args.length != 1) { + System.out.println("Usage: "); + return; + } + + Design design = Design.readCheckpoint(args[0]); + + DesignTools.makePhysNetNamesConsistent(design); + DesignTools.createMissingSitePinInsts(design); + DesignTools.updatePinsIsRouted(design); + + int fullyRoutedNetCount = 0; + int partiallyRoutedNetCount = 0; + + for (Net net : design.getNets()) { + if (!net.hasPIPs()) continue; + + boolean isPartiallyRouted = false; + ArrayList unroutedPins = new ArrayList<>(); + + for (SitePinInst pin : net.getPins()) { + if (!pin.isRouted() && !pin.isOutPin()) { + unroutedPins.add(pin.getName()); + isPartiallyRouted = true; + } + } + + if (!unroutedPins.isEmpty()) { + System.out.println("Net " + net.getName() + " has unrouted pins: " + unroutedPins); + } + + if (isPartiallyRouted) { + partiallyRoutedNetCount++; + } else { + fullyRoutedNetCount++; + } + } + + System.out.println("Fully routed nets: " + fullyRoutedNetCount); + System.out.println("Partially routed nets: " + partiallyRoutedNetCount); + } +} diff --git a/src/com/xilinx/rapidwright/ipi/BlockStitcher.java b/src/com/xilinx/rapidwright/ipi/BlockStitcher.java index 2d84eaf58..0b7de6d52 100644 --- a/src/com/xilinx/rapidwright/ipi/BlockStitcher.java +++ b/src/com/xilinx/rapidwright/ipi/BlockStitcher.java @@ -107,16 +107,17 @@ public class BlockStitcher { private ArrayList getPassThruPortInsts(Port port, EDIFHierPortInst curr) { ArrayList list = new ArrayList<>(); for (String name : port.getPassThruPortNames()) { - EDIFPortInst passThruInst = curr.getPortInst().getCellInst().getPortInst(name); + EDIFHierCellInst hierInst = curr.getHierarchicalInst(); + EDIFPortInst passThruInst = hierInst.getInst().getPortInst(name); if (passThruInst == null) { - String unconnected = curr.getPortInst().getCellInst().getName() + EDIFTools.EDIF_HIER_SEP + name; + String unconnected = hierInst.getInst().getName() + EDIFTools.EDIF_HIER_SEP + name; if (!reportedUnconnects.contains(unconnected)) { System.out.println("INFO: Port " + unconnected + " is unconnected"); reportedUnconnects.add(unconnected); } continue; } - EDIFHierPortInst passThru = new EDIFHierPortInst(curr.getHierarchicalInst(), passThruInst); + EDIFHierPortInst passThru = new EDIFHierPortInst(hierInst.getParent(), passThruInst); list.add(passThru); } return list; @@ -462,7 +463,7 @@ public static void main(String[] args) { } t.stop().start("Reading Top Level EDIF"); EDIFNetlist topEdifNetlist = EDIFTools.readEdifFile(args[1]); - Design stitched = new Design("top_stitched", stitcher.partName); + Design stitched = new Design(topEdifNetlist.getName(), stitcher.partName); stitched.setNetlist(topEdifNetlist); t.stop().start("Implement Blocks"); stitcher.populateModuleInstMaps(topEdifNetlist); @@ -534,7 +535,7 @@ public static void main(String[] args) { } //System.out.println(modInstName + " " + implementationIndex); EDIFNetlist tmp = stitched.getNetlist(); - stitched.setNetlist(null); + stitched.setNetlist(EDIFTools.createNewNetlist("dummy")); ModuleInst mi = stitched.createModuleInst(modInstName, modImpls.get(implementationIndex)); stitched.setNetlist(tmp); miMap.put(mi, modImpls.getNetlist()); @@ -590,18 +591,19 @@ public static void main(String[] args) { inst.setCellType(cellType); } - for (EDIFCell c : stitched.getNetlist().getLibrary("IP_Integrator_Lib").getCells()) { - work.addCell(c); - } - - ArrayList libsToRemove = new ArrayList<>(); - for (EDIFLibrary lib : stitched.getNetlist().getLibraries()) { - if (lib.getName().equals(EDIFTools.EDIF_LIBRARY_HDI_PRIMITIVES_NAME) || - lib.getName().equals(EDIFTools.EDIF_LIBRARY_WORK_NAME)) continue; - libsToRemove.add(lib.getName()); - } - for (String lib : libsToRemove) { - stitched.getNetlist().removeLibrary(lib); + // Move all cells to 'work' library + boolean renameCollisions = true; + stitched.getNetlist().consolidateAllToWorkLibrary(renameCollisions); + EDIFLibrary workLib = stitched.getNetlist().getWorkLibrary(); + for (EDIFCell workCell : workLib.getCells()) { + for (EDIFCellInst inst : workCell.getCellInsts()) { + EDIFLibrary lib = inst.getCellType().getLibrary(); + if (lib.isHDIPrimitivesLibrary() || lib == workLib) { + continue; + } else { + inst.setCellType(work.getCell(inst.getCellType().getName())); + } + } } t.stop(); if (DUMP_SYNTH_DCP_ONLY) { diff --git a/src/com/xilinx/rapidwright/util/CodeGenerator.java b/src/com/xilinx/rapidwright/util/CodeGenerator.java index 4f9fcba15..d84f102f1 100644 --- a/src/com/xilinx/rapidwright/util/CodeGenerator.java +++ b/src/com/xilinx/rapidwright/util/CodeGenerator.java @@ -22,18 +22,31 @@ package com.xilinx.rapidwright.util; +import java.io.FileNotFoundException; +import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import com.xilinx.rapidwright.design.AltPinMapping; +import com.xilinx.rapidwright.design.Cell; import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.Net; import com.xilinx.rapidwright.design.SiteInst; import com.xilinx.rapidwright.design.SitePinInst; +import com.xilinx.rapidwright.design.Unisim; +import com.xilinx.rapidwright.device.BELPin; import com.xilinx.rapidwright.device.Device; import com.xilinx.rapidwright.device.PIP; +import com.xilinx.rapidwright.device.SitePIP; +import com.xilinx.rapidwright.device.SiteTypeEnum; +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFHierPortInst; +import com.xilinx.rapidwright.edif.EDIFPortInst; +import com.xilinx.rapidwright.edif.EDIFPropertyValue; /** * This utility class is used to create RapidWright code from a DCP file that is tedious to create by hand. @@ -174,4 +187,249 @@ public static void addPIPs(Net net, String[] pips) { net.addPIP(device.getPIP(pip)); } } + + /** + * Shorthand method to route site wires with a particular net in a site + * instance. + * + * @param si The site instance to target + * @param n The net to be site routed + * @param siteWires The list of site wire names to route + */ + public static void routeSiteNet(SiteInst si, Net n, String... siteWires) { + for (String siteWire : siteWires) { + BELPin bp = si.getSiteWirePins(siteWire)[0]; + si.routeIntraSiteNet(n, bp, bp); + } + } + + /** + * Shorthand method for turning on site PIPs in a site instance. + * + * @param si The target site instance. + * @param sitePIPs The list of site PIPs (of the format :) to turn on. + */ + public static void addSitePIPs(SiteInst si, String... sitePIPs) { + for (String sitePIP : sitePIPs) { + int colonIdx = sitePIP.indexOf(':'); + si.addSitePIP(sitePIP.substring(0, colonIdx), sitePIP.substring(colonIdx + 1)); + } + } + + /** + * Shorthand method for creating a placed cell inside a site instance. This is + * mostly useful for constructing test case scenarios. + * + * @param si The target site instance + * @param name Name of the cell to create + * @param isRoutethru Flag indicating if the cell should be a routethru + * @param type The type of the cell (Unisim or special field) + * @param bel The name of the BEL where the cell should be placed + * @param pinMaps A variable number of pin mappings for the cell where each + * string is of the format : + * @return The created cell. + */ + public static Cell genCell(SiteInst si, String name, boolean isRoutethru, String type, String bel, + String... pinMaps) { + Cell c = null; + if (isRoutethru || type.startsWith("<")) { + c = new Cell(name, si.getBEL(bel)); + c.setSiteInst(si); + si.getCellMap().put(bel, c); + c.setType(type); + c.setRoutethru(isRoutethru); + } else { + c = si.getDesign().createAndPlaceCell(si.getDesign().getTopEDIFCell(), name, Unisim.valueOf(type), + si.getSite(), si.getBEL(bel)); + } + for (String pinMap : pinMaps) { + int colonIdx = pinMap.indexOf(':'); + c.addPinMapping(pinMap.substring(0, colonIdx), pinMap.substring(colonIdx + 1)); + } + return c; + } + + private static int uniqueCount = 0; + + public static String TEST_SITE_INST_CLASS_NAME = "GenTestSiteInstDesign"; + public static String TEST_SITE_INST_METHOD_NAME = "genTestSiteInstDesign"; + + /** + * Creates boilerplate code for a test by re-creating a SiteInst's configuration + * from scratch in RapidWright APIs. This is useful for testing when only a + * small context is necessary to reproduce a specific scenario. + * + * @param inst The instance to replicate in the test + * @param ps The PrintStream (System.out, or a file-based + * PrintStream, for example). + * @param simplifyNames Flag indicating that the cell and and net names should + * be simplified. + */ + public static void genCodeForTestSite(SiteInst inst, PrintStream ps, boolean includeRouting) { + Design design = inst.getDesign(); + String part = design.getPartName(); + String siteName = inst.getSiteName(); + Map nameMap = new HashMap<>(); + + for (String c : new String[] {"design.AltPinMapping", "design.Cell", "design.Design", + "design.Net", "design.NetType", "design.SiteInst", "device.Device", + "device.SiteTypeEnum", "edif.EDIFCell", "edif.EDIFDirection", "edif.EDIFHierCellInst", + "edif.EDIFHierPortInst", "edif.EDIFNetlist", "edif.EDIFTools", "edif.EDIFValueType", + "util.CodeGenerator" }) { + ps.println("import com.xilinx.rapidwright."+c+";"); + } + + ps.println(""); + ps.println("public class " + TEST_SITE_INST_CLASS_NAME + " {"); + ps.println(" public Design " + TEST_SITE_INST_METHOD_NAME + "() {"); + ps.println(" Design design = new Design(\"test\", \"" + part + "\");"); + ps.println(" Device device = design.getDevice();"); + ps.println(" SiteInst si = design.createSiteInst(\"" + siteName + "\", SiteTypeEnum." + + inst.getSiteTypeEnum() + + ", device.getSite(\"" + + siteName + "\"));"); + ps.println(" EDIFNetlist netlist = design.getNetlist();"); + ps.println(" EDIFCell top = netlist.getTopCell();"); + String tab = " "; + for (Cell c : inst.getCells()) { + String newCellName = nameMap.computeIfAbsent(c.getName(), n -> ("cell" + uniqueCount++)); + nameMap.put(c.getName(), newCellName); + + String varName = newCellName + ((c.isRoutethru() || c.getType().startsWith("<")) ? "_" + c.getBELName() : ""); + + ps.print(tab + "Cell "+varName+" = CodeGenerator.genCell(si, \"" + newCellName + "\", " + c.isRoutethru() + ", \"" + + c.getType() + "\", \"" + c.getBELName() + "\""); + + String[] physPinMappings = c.getPhysicalPinMappings(); + for (int i = 0; i < physPinMappings.length; i++) { + String physPinName = c.getBEL().getPin(i).getName(); + String logPinName = physPinMappings[i]; + ps.print(", \"" + physPinName + ":" + logPinName + "\""); + } + ps.println(");"); + + for (Entry apm : c.getAltPinMappings().entrySet()) { + ps.println(tab + varName + ".addAltPinMapping(\"" + apm.getKey() + "\",new AltPinMapping(\"" + +apm.getValue().getLogicalName()+"\", \"" + +nameMap.computeIfAbsent(apm.getValue().getAltCellName(), n -> ("cell" + uniqueCount++))+"\",\"" + +apm.getValue().getAltCellType()+"\"));"); + } + if (!c.isRoutethru()) { + for (Entry e : c.getProperties().entrySet()) { + ps.println(tab + varName + ".addProperty(\"" + e.getKey() + "\", \"" + e.getValue().getValue() + + "\", EDIFValueType." + e.getValue().getType().name() + ");"); + } + } + for (String fixPin : c.getPhysicalPinMappings()) { + if (c.isPinFixed(fixPin)) { + ps.println(tab + varName + ".fixPin(\""+fixPin+"\");"); + } + } + SiteTypeEnum altBlockedType = c.getAltBlockedSiteType(); + if (altBlockedType != null) { + ps.println(tab + varName + ".setAltBlockedSiteType(SiteTypeEnum." + altBlockedType.name()+");"); + } + + if (c.isBELFixed()) ps.println(tab + varName + ".setBELFixed(true);"); + if (c.isLocked()) ps.println(tab + varName + ".setLocked(true);"); + if (c.isNullBEL()) ps.println(tab + varName + ".setNullBEL(true);"); + if (c.isSiteFixed()) ps.println(tab + varName + ".setSiteFixed(true);"); + } + + ps.println(); + List usedSitePIPs = inst.getUsedSitePIPs(); + for (int i = 0; i < usedSitePIPs.size(); i++) { + SitePIP p = usedSitePIPs.get(i); + ps.print(i == 0 ? tab + "CodeGenerator.addSitePIPs(si, " : ", "); + ps.print("\"" + p.getBELName() + ":" + p.getInputPinName() + "\""); + if (i == usedSitePIPs.size() - 1) { + ps.println(");"); + } + } + ps.println(); + + for (Entry> e : inst.getNetToSiteWiresMap().entrySet()) { + Net n = e.getKey(); + boolean hasSrc = false; + boolean hasSnk = false; + String newNetName = (n.isStaticNet() || n.isUsedNet()) ? null + : nameMap.computeIfAbsent(n.getName(), p -> ("net" + uniqueCount++)); + if (newNetName == null) { + if (n.isStaticNet()) { + newNetName = n.isVCCNet() ? "vcc" : "gnd"; + ps.println(tab + "Net " + newNetName + " = design." + (n.isVCCNet() ? "getVccNet()" : "getGndNet()") + + ";"); + for (Cell c : inst.getCells()) { + if (!c.isRoutethru()) { + EDIFHierCellInst ci = c.getEDIFHierCellInst(); + for (EDIFPortInst pi : ci.getInst().getPortInsts()) { + if ((pi.getNet().isVCC() && n.isVCCNet()) || (pi.getNet().isGND() && n.isGNDNet())) { + + ps.println(tab + "EDIFTools.getStaticNet(NetType." + + (pi.getNet().isVCC() ? "VCC" : "GND") + + ", top, netlist).createPortInst(\"" + pi.getName() + "\", " + + nameMap.get(c.getName()) + ");"); + } + } + } + } + } else if (n.isUsedNet()) { + newNetName = "usedNet"; + ps.println(tab + "Net " + newNetName + " = design.createNet(Net.USED_NET);"); + } else { + throw new RuntimeException("ERROR: Unhandled Net type"); + } + } else { + ps.println(tab + "Net " + newNetName + " = design.createNet(\"" + newNetName + "\");"); + if (n.getLogicalHierNet() != null) { + for (EDIFHierPortInst pi : n.getLogicalHierNet().getLeafHierPortInsts()) { + String newCellName = nameMap.get(pi.getFullHierarchicalInstName()); + if (newCellName != null) { + ps.println(tab + newNetName + ".getLogicalNet().createPortInst(\"" + + pi.getPortInst().getName() + + "\", " + newCellName + ");"); + if (pi.isOutput()) { + hasSrc = true; + } else { + hasSnk = true; + } + } + } + if (!hasSrc) { + ps.println(tab + "top.getNet(\"" + newNetName + "\").createPortInst(top.createPort(\"port" + + (uniqueCount++) + "\", EDIFDirection.INPUT, 1));"); + } + if (!hasSnk) { + ps.println(tab + "top.getNet(\"" + newNetName + "\").createPortInst(top.createPort(\"port" + + (uniqueCount++) + "\", EDIFDirection.OUTPUT, 1));"); + } + } + } + + for (SitePinInst pin : n.getPins()) { + if (pin.getSiteInst() == inst) { + ps.println(tab + newNetName + ".createPin(\"" + pin.getName() + "\", si);"); + } + } + if (includeRouting) { + ps.println(tab + "CodeGenerator.addPIPs(" + newNetName + ", new String[] {"); + for (PIP p : n.getPIPs()) { + ps.println(tab + "\"" + p.toString() + "\","); + } + ps.println(tab + "});"); + } + ps.print(tab + "CodeGenerator.routeSiteNet(si, " + newNetName + " "); + for (String siteWire : e.getValue()) { + ps.print(", \"" + siteWire + "\""); + } + ps.println(");"); + } + ps.println(tab + "design.setDesignOutOfContext(true);"); + ps.println(tab + "design.setAutoIOBuffers(false);"); + ps.println(tab + "return design;"); + ps.println(" }"); + ps.println("}"); + } } diff --git a/test/src/com/xilinx/rapidwright/design/TestDesignTools.java b/test/src/com/xilinx/rapidwright/design/TestDesignTools.java index 71babfe5f..694bc4618 100644 --- a/test/src/com/xilinx/rapidwright/design/TestDesignTools.java +++ b/test/src/com/xilinx/rapidwright/design/TestDesignTools.java @@ -1317,6 +1317,16 @@ public void testUnrouteCellPinSiteRouting() { Assertions.assertNull(si4.getNetFromSiteWire("FFMUXB1_OUT1")); Assertions.assertNull(si4.getUsedSitePIP("FFMUXB1")); Assertions.assertNull(si4.getNetFromSiteWire("B_O")); + + // Test false connection when logical pin is not connected + Cell carry5 = design.createAndPlaceCell("carry5", Unisim.CARRY8, "SLICE_X0Y5/CARRY8"); + Cell lut5 = design.createAndPlaceCell("lut5", Unisim.LUT1, "SLICE_X0Y5/D6LUT"); + Net net5 = design.createNet("lut5_output"); + net5.connect(lut5, "O"); + + Assertions.assertEquals(net5, carry5.getSiteInst().getNetFromSiteWire("D_O")); + List pinsToRemove = DesignTools.unrouteCellPinSiteRouting(carry5, "S[3]"); + Assertions.assertEquals(0, pinsToRemove.size()); } @Test @@ -1534,4 +1544,18 @@ void testGetRoutedSitePin(String hierPortInstName, String expected) { String sitePinName = DesignTools.getRoutedSitePin(cell, net, ehpi.getPortInst().getName()); Assertions.assertEquals(expected, sitePinName == null ? "null" : sitePinName); } + + @Test + public void testUnrouteCellPinSiteRoutingBRAMClkPins() { + Design d = RapidWrightDCP.loadDCP("microblazeAndILA_3pblocks.dcp"); + Cell c = d.getCell( + "base_mb_i/microblaze_0_local_memory/lmb_bram/U0/inst_blk_mem_gen/gnbram.gnative_mem_map_bmg.native_mem_map_blk_mem_gen/valid.cstr/ramloop[5].ram.r/prim_noinit.ram/DEVICE_8SERIES.WITH_BMM_INFO.TRUE_DP.SIMPLE_PRIM36.SERIES8_TDP_SP36_NO_ECC_ATTR.ram"); + + List unrouted = DesignTools.unrouteCellPinSiteRouting(c, "CLKARDCLK"); + Assertions.assertEquals(2, unrouted.size()); + for (SitePinInst p : unrouted) { + Assertions.assertTrue(p.getName().equals("CLKAU_X") || p.getName().equals("CLKAL_X")); + } + + } } diff --git a/test/src/com/xilinx/rapidwright/eco/TestECOTools.java b/test/src/com/xilinx/rapidwright/eco/TestECOTools.java index 3fca74dc4..e893c3d6b 100644 --- a/test/src/com/xilinx/rapidwright/eco/TestECOTools.java +++ b/test/src/com/xilinx/rapidwright/eco/TestECOTools.java @@ -591,4 +591,38 @@ public void testRemoveCellVersal() { Assertions.assertNull(si.getUsedSitePIP("CLKINV")); } + + @Test + public void testRemoveCellNeighborRouteThru() { + // This is a corner case that involves a CARRY8 in US+ and a LUT removal from + // A6LUT that would prevent the routethru removal later when the CARRY8 was + // removed. + Design d = new Design("test", "xcku9p-ffve900-2-e"); + String siteName = "SLICE_X96Y231"; + Cell carry = d.createAndPlaceCell("carry", Unisim.CARRY8, siteName + "/CARRY8"); + SiteInst si = carry.getSiteInst(); + Cell lut1 = d.createAndPlaceCell("lut1", Unisim.LUT1, siteName + "/A6LUT"); + Cell ff = d.createAndPlaceCell("ff", Unisim.FDRE, siteName + "/HFF"); + Cell ff2 = d.createAndPlaceCell("ff2", Unisim.FDRE, siteName + "/AFF"); + Net lut1Input = d.createNet("lut1Input"); + lut1Input.connect(lut1, "I0"); + lut1Input.connect(ff2, "Q"); + si.routeIntraSiteNet(lut1Input, lut1.getBEL().getPin("A6"), si.getBELPin("A6", "A6")); + Net carryS0 = d.createNet("carryS0"); + carryS0.connect(lut1, "O"); + carryS0.connect(carry, "S[0]"); + si.routeIntraSiteNet(carryS0, lut1.getBEL().getPin("O6"), carry.getBEL().getPin("S0")); + Net routethruNet = d.createNet("routethruNet"); + routethruNet.connect(ff, "Q"); + routethruNet.connect(carry, "S[7]"); + si.routeIntraSiteNet(routethruNet, ff.getBEL().getPin("Q"), si.getBELPin("HQ", "HQ")); + si.routeIntraSiteNet(routethruNet, si.getBELPin("H6", "H6"), carry.getBEL().getPin("S7")); + + List remove = new ArrayList<>(); + remove.add(lut1.getEDIFHierCellInst()); + ECOTools.removeCell(d, remove, null); + + // Ensure the net stays routed after LUT1 is removed from A6LUT + Assertions.assertEquals(si.getNetFromSiteWire("H6"), routethruNet); + } } diff --git a/test/src/com/xilinx/rapidwright/edif/TestEDIFNetlist.java b/test/src/com/xilinx/rapidwright/edif/TestEDIFNetlist.java index c4e716958..f0931b2e7 100644 --- a/test/src/com/xilinx/rapidwright/edif/TestEDIFNetlist.java +++ b/test/src/com/xilinx/rapidwright/edif/TestEDIFNetlist.java @@ -315,9 +315,30 @@ public void testCopyCellsAndSubCellsCollision() { dstNetlist.copyCellAndSubCells(srcNetlist.getTopCell()); RuntimeException e = Assertions.assertThrows(RuntimeException.class, - () -> dstNetlist.copyCellAndSubCells(srcNetlist.getTopCell())); + () -> dstNetlist.copyCellAndSubCells(srcNetlist.getTopCell(), + /* uniquifyCollisions= */false)); Assertions.assertEquals("ERROR: Destination netlist already contains EDIFCell named 'picoblaze_top' in library 'work'", e.getMessage()); + + Assertions.assertEquals(dstNetlist.getHDIPrimitivesLibrary().getCellMap().size(), + srcNetlist.getHDIPrimitivesLibrary().getCellMap().size()); + Assertions.assertEquals(dstNetlist.getWorkLibrary().getCellMap().size() - 1, + srcNetlist.getWorkLibrary().getCellMap().size()); + + dstNetlist.copyCellAndSubCells(srcNetlist.getTopCell(), /* uniquifyCollisions= */true); + + Assertions.assertEquals(dstNetlist.getHDIPrimitivesLibrary().getCellMap().size(), + srcNetlist.getHDIPrimitivesLibrary().getCellMap().size()); + Assertions.assertEquals(dstNetlist.getWorkLibrary().getCellMap().size() - 1, + 2 * srcNetlist.getWorkLibrary().getCellMap().size()); + + dstNetlist.copyCellAndSubCells(srcNetlist.getTopCell(), /* uniquifyCollisions= */true); + + Assertions.assertEquals(dstNetlist.getHDIPrimitivesLibrary().getCellMap().size(), + srcNetlist.getHDIPrimitivesLibrary().getCellMap().size()); + Assertions.assertEquals(dstNetlist.getWorkLibrary().getCellMap().size() - 1, + 3 * srcNetlist.getWorkLibrary().getCellMap().size()); + } @ParameterizedTest diff --git a/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java b/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java index 56340954b..525359017 100644 --- a/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java +++ b/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java @@ -23,11 +23,13 @@ package com.xilinx.rapidwright.edif; +import java.nio.file.Path; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.device.Device; @@ -153,4 +155,27 @@ void testGetBusName(String portName, String busName, int width) { EDIFPort busOutput = cell.createPort(portName, EDIFDirection.OUTPUT, width); Assertions.assertEquals(busName, busOutput.getBusName()); } + + @Test + public void testExportOfSingleBitBusses(@TempDir Path tempDir) { + final EDIFNetlist netlist = EDIFTools.createNewNetlist("testExportOfSingleBitBusses"); + String cellName = "cell_1"; + String portName = "single_bit_bus[0:0]"; + EDIFCell cell = new EDIFCell(netlist.getWorkLibrary(), cellName); + EDIFPort busOutput = cell.createPort(portName, EDIFDirection.OUTPUT, 1); + + Assertions.assertTrue(busOutput.isBus()); + Assertions.assertEquals(1, busOutput.getWidth()); + + Path edifOutput = tempDir.resolve("output.edf"); + netlist.exportEDIF(edifOutput); + + EDIFNetlist readEdif = EDIFTools.readEdifFile(edifOutput); + + // Ensure export and parsing of EDIF single bit bus results in a single bit bus + EDIFPort port = readEdif.getCell(cellName).getPort(busOutput.getBusName()); + Assertions.assertNotNull(port); + Assertions.assertTrue(port.isBus()); + Assertions.assertEquals(1, port.getWidth()); + } } diff --git a/test/src/com/xilinx/rapidwright/util/TestCodeGenerator.java b/test/src/com/xilinx/rapidwright/util/TestCodeGenerator.java index 04742688b..9a85aec1a 100644 --- a/test/src/com/xilinx/rapidwright/util/TestCodeGenerator.java +++ b/test/src/com/xilinx/rapidwright/util/TestCodeGenerator.java @@ -22,12 +22,31 @@ package com.xilinx.rapidwright.util; +import java.io.FileNotFoundException; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Map.Entry; + +import javax.tools.JavaCompiler; +import javax.tools.ToolProvider; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import com.xilinx.rapidwright.design.Cell; +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.Net; +import com.xilinx.rapidwright.design.SiteInst; +import com.xilinx.rapidwright.device.BEL; import com.xilinx.rapidwright.support.RapidWrightDCP; public class TestCodeGenerator { @@ -112,4 +131,65 @@ public void testTestNetGenerator() { + "\n"; Assertions.assertEquals(expectedString, actualString); } + + @Test + public void testGenCodeForTestSite(@TempDir Path tempDir) { + Design d = RapidWrightDCP.loadDCP("microblazeAndILA_3pblocks.dcp"); + SiteInst si = d.getSiteInstFromSiteName("SLICE_X36Y97"); + + Path javaFile = tempDir.resolve(CodeGenerator.TEST_SITE_INST_CLASS_NAME + ".java"); + try (PrintStream ps = new PrintStream(javaFile.toString())) { + CodeGenerator.genCodeForTestSite(si, ps, false); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + // Compiler should return 0 if code compiled successfully + Assertions.assertEquals(0, compiler.run(null, null, null, javaFile.toString())); + + try { + // Run the generated code and extract the Design object + URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { tempDir.toFile().toURI().toURL() }); + Class testClass = Class.forName(CodeGenerator.TEST_SITE_INST_CLASS_NAME, true, classLoader); + Method testMethod = testClass.getMethod(CodeGenerator.TEST_SITE_INST_METHOD_NAME); + Design testDesign = (Design) testMethod.invoke(testClass.getDeclaredConstructor().newInstance()); + + // Compare against the original SiteInst + SiteInst testSiteInst = testDesign.getSiteInstFromSiteName(si.getSiteName()); + Assertions.assertNotNull(testSiteInst); + Assertions.assertEquals(si.getCells().size(), testSiteInst.getCells().size()); + for (Cell c : si.getCells()) { + BEL bel = c.getBEL(); + Cell testCell = testSiteInst.getCell(bel); + Assertions.assertEquals(c.getType(), testCell.getType()); + + Assertions.assertEquals(Arrays.toString(c.getPhysicalPinMappings()), + Arrays.toString(testCell.getPhysicalPinMappings())); + for (Entry e : c.getPinMappingsP2L().entrySet()) { + Assertions.assertEquals(e.getValue(), testCell.getLogicalPinMapping(e.getKey())); + } + Assertions.assertEquals(c.isBELFixed(), testCell.isBELFixed()); + Assertions.assertEquals(c.isSiteFixed(), testCell.isSiteFixed()); + Assertions.assertEquals(c.isCellFixed(), testCell.isCellFixed()); + Assertions.assertEquals(c.isRoutethru(), testCell.isRoutethru()); + } + + for (Entry> e : si.getNetToSiteWiresMap().entrySet()) { + // Net names will not match, so we just match the site wire sets + Net equivalentNet = testSiteInst.getNetFromSiteWire(e.getValue().get(0)); + List testSiteWires = testSiteInst.getSiteWiresFromNet(equivalentNet); + String[] gold = e.getValue().toArray(new String[e.getValue().size()]); + Arrays.sort(gold); + String[] test = testSiteWires.toArray(new String[testSiteWires.size()]); + Arrays.sort(test); + Assertions.assertTrue(Arrays.deepEquals(gold, test)); + } + + } catch (MalformedURLException | ClassNotFoundException | NoSuchMethodException | SecurityException + | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | InstantiationException e) { + throw new RuntimeException(e); + } + } } From 8b55a0a9ead6e3c2d41891c92a93ebcf40ef573c Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 10 Apr 2025 13:03:14 -0700 Subject: [PATCH 02/24] WIP - ArrayBuilder Signed-off-by: Chris Lavin --- .../xilinx/rapidwright/design/ClockTools.java | 102 ++++++ .../xilinx/rapidwright/design/NetTools.java | 33 ++ .../design/blocks/PBlockGenerator.java | 83 +++-- .../design/blocks/UtilizationType.java | 143 +++++++- .../design/tools/ArrayBuilder.java | 325 ++++++++++++++++++ src/com/xilinx/rapidwright/device/Series.java | 2 +- .../rapidwright/util/PerformanceExplorer.java | 99 +++++- .../xilinx/rapidwright/util/VivadoTools.java | 57 ++- .../design/blocks/TestUtilizationType.java | 30 ++ 9 files changed, 808 insertions(+), 66 deletions(-) create mode 100644 src/com/xilinx/rapidwright/design/ClockTools.java create mode 100644 src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java create mode 100644 test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java diff --git a/src/com/xilinx/rapidwright/design/ClockTools.java b/src/com/xilinx/rapidwright/design/ClockTools.java new file mode 100644 index 000000000..25063c0e9 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/ClockTools.java @@ -0,0 +1,102 @@ +/* + * + * 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.design; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFHierNet; +import com.xilinx.rapidwright.edif.EDIFHierPortInst; +import com.xilinx.rapidwright.edif.EDIFNetlist; + +public class ClockTools { + + public static Map unisimFFs; + + public static Map unisimLatches; + + public static Map clbRegTypes; + + static { + unisimFFs = new HashMap<>(); + unisimFFs.put("FDRE", "C"); + unisimFFs.put("FDSE", "C"); + unisimFFs.put("FDCE", "C"); + unisimFFs.put("FDPE", "C"); + + unisimLatches = new HashMap<>(); + unisimLatches.put("LDCE", "G"); + unisimLatches.put("LDPE", "G"); + + clbRegTypes = new HashMap<>(); + clbRegTypes.putAll(unisimFFs); + clbRegTypes.putAll(unisimLatches); + } + + /** + * If the provided instance is a CLB register (flip-flop or latch), it will get + * the net of the connected net to the clock pin of the cell and return it. + * + * @param i The instance in question. + * @return The clock net connected to this instance's clock pin, or null if none + * is found. + */ + public static EDIFHierNet getClockNet(EDIFHierCellInst i) { + String clkInput = clbRegTypes.get(i.getCellName()); + EDIFHierPortInst portInst = clkInput != null ? i.getPortInst(clkInput) : null; + return portInst != null ? portInst.getHierarchicalNet() : null; + } + + /** + * Gets the clock net from the provided design. If the design has more than one + * net, it gets the net with the most CLB register fan out. + * + * @param design The design to query. + * @return The biggest CLB register fan out net in the design. + */ + public static EDIFHierNet getClockFromDesign(Design design) { + EDIFNetlist netlist = design.getNetlist(); + Map clockSinkCounts = new HashMap<>(); + for (EDIFHierCellInst i : netlist.getAllLeafHierCellInstances()) { + EDIFHierNet clk = ClockTools.getClockNet(i); + clk = netlist.getParentNet(clk); + if (clk != null) { + clockSinkCounts.compute(clk, (k, v) -> v == null ? 1 : 1 + v); + } + } + + int largestFanout = 0; + EDIFHierNet clkLargestFanout = null; + for (Entry e : clockSinkCounts.entrySet()) { + if (e.getValue() > largestFanout) { + largestFanout = e.getValue(); + clkLargestFanout = e.getKey(); + } + } + + return clkLargestFanout; + } + +} diff --git a/src/com/xilinx/rapidwright/design/NetTools.java b/src/com/xilinx/rapidwright/design/NetTools.java index 8b8658780..baf6ff420 100644 --- a/src/com/xilinx/rapidwright/design/NetTools.java +++ b/src/com/xilinx/rapidwright/design/NetTools.java @@ -158,4 +158,37 @@ public static boolean hasClockSinks(Net net) { } return false; } + + /** + * Unroutes routed nets that have one or more overlapping or conflicting nodes + * with another route in the design. The choice between which of two or more + * nets gets unrouted is arbitrary and nets are unrouted until the set of routed + * nets do not overlap. + * + * @param design The design to evaluate for conflicting nodes. + * @return The list of nets that were unrouted. + */ + public static List unrouteNetsWithOverlappingNodes(Design design) { + List unroutedNets = new ArrayList<>(); + Map used = new HashMap<>(); + for (Net net : design.getNets()) { + for (PIP pip : net.getPIPs()) { + for (Node node : new Node[] { pip.getStartNode(), pip.getEndNode() }) { + if (node == null) + continue; + Net existing = used.putIfAbsent(node, net); + if (existing != null && existing != net) { + for (PIP oldPip : new ArrayList<>(existing.getPIPs())) { + for (Node oldNode : new Node[] { oldPip.getStartNode(), oldPip.getEndNode() }) { + used.remove(oldNode); + } + } + existing.unroute(); + unroutedNets.add(existing); + } + } + } + } + return unroutedNets; + } } diff --git a/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java b/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java index 4b35c2088..e7ceb8a99 100644 --- a/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java +++ b/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java @@ -46,7 +46,6 @@ import com.xilinx.rapidwright.device.FamilyType; import com.xilinx.rapidwright.device.Part; import com.xilinx.rapidwright.device.PartNameTools; -import com.xilinx.rapidwright.device.SLR; import com.xilinx.rapidwright.device.Series; import com.xilinx.rapidwright.device.Site; import com.xilinx.rapidwright.device.SiteTypeEnum; @@ -68,8 +67,8 @@ public class PBlockGenerator { public static final int LUTS_PER_CLE = 8; public static final int FF_PER_CLE = 16; - public static final float CLES_PER_BRAM = 5; - public static final float CLES_PER_DSP = 2.5f; + public static float CLES_PER_BRAM = 5; + public static float CLES_PER_DSP = 2.5f; public static int RAMLUTS_PER_CLE = 8; public static int CARRY_PER_CLE = 1; public static int SLICES_PER_TILE = 1; @@ -106,6 +105,7 @@ public class PBlockGenerator { int carryCount = 0; int bram18kCount = 0; int bram36kCount = 0; + int uramCount = 0; int tallestShape = 0; int widestShape = 0; int shapeArea = 0; @@ -151,14 +151,20 @@ private void getResourceUsages(String reportFileName) { lutCount = Integer.parseInt(line.split("\\s+")[4]); } else if (line.startsWith("| CLB Registers") || line.startsWith("| Slice Registers")) { regCount = Integer.parseInt(line.split("\\s+")[4]); + } else if (line.startsWith("| Registers")) { + regCount = Integer.parseInt(line.split("\\s+")[3]); } else if (line.startsWith("| LUT as Memory")) { lutRAMCount = Integer.parseInt(line.split("\\s+")[5]); - } else if (line.startsWith("| RAMB36/FIFO")) { + } else if (line.startsWith("| RAMB36/FIFO") || line.startsWith("| RAMB36E5")) { bram36kCount = Integer.parseInt(line.split("\\s+")[3]); - } else if (line.startsWith("| RAMB18")) { + } else if (line.startsWith("| RAMB18") || line.startsWith("| RAMB18E5*")) { bram18kCount = Integer.parseInt(line.split("\\s+")[3]); + } else if (line.startsWith("| URAM")) { + uramCount = Integer.parseInt(line.split("\\s+")[3]); } else if (line.startsWith("| DSPs")) { dspCount = Integer.parseInt(line.split("\\s+")[3]); + } else if (line.startsWith("| DSP Slices")) { + dspCount = Integer.parseInt(line.split("\\s+")[4]); } else if (line.startsWith("| CARRY")) { carryCount = Integer.parseInt(line.split("\\s+")[3]); } @@ -169,6 +175,10 @@ private void getResourceUsages(String reportFileName) { CARRY_PER_CLE = 2; RAMLUTS_PER_CLE = 4; SLICES_PER_TILE = 2; + } else if (dev.getSeries() == Series.Versal) { + CLES_PER_DSP = 1f; + CLES_PER_BRAM = 4f; + SLICES_PER_TILE = 2; } if (debug) { System.out.println("Parsed report: " + reportFileName); @@ -758,8 +768,15 @@ private ArrayList getCompatiblePatterns(int sliceColumns, int private String generatePblock(Site upperLeft, int columns, int rows) { String siteTypeName = upperLeft.getNameSpacePrefix(); - return siteTypeName+"X" + upperLeft.getInstanceX() + "Y" + (upperLeft.getInstanceY()-(rows-1)) + - ":"+siteTypeName+"X" + (upperLeft.getInstanceX()+columns-1) + "Y" + upperLeft.getInstanceY(); + int x = (upperLeft.getInstanceX() + columns - 1); + int y = (upperLeft.getInstanceY() + rows - 1); + String pblock = siteTypeName + "X" + upperLeft.getInstanceX() + "Y" + y + ":" + siteTypeName + "X" + x + "Y" + + upperLeft.getInstanceY(); + if (x < 0 || y < 0) { + throw new RuntimeException("ERROR: Invalid pblock generated: " + pblock); + } + + return pblock; } public ArrayList generatePBlockFromReport2(String reportFileName, String shapesReportFileName) { @@ -924,6 +941,13 @@ public ArrayList generatePBlockFromReport(String reportFileName, String } } + // Versal devices have a 50/50 mix of SLICEM and SLICEL with two SLICEs (one of + // each) in each tile + if (dev.getSeries() == Series.Versal) { + numSLICEColumns = numSLICEColumns + numSLICEMColumns; + numSLICEMColumns = 0; + } + // Fail safe in case we get too short, make sure shapes (carry chains,etc) can fit if (tallestShape > pblockCLEHeight) { pblockCLEHeight = tallestShape; @@ -1093,12 +1117,13 @@ public ArrayList generatePBlockFromReport(String reportFileName, String if (numSLICEColumns > 0 || numSLICEMColumns > 0) { int pIdx = 0; for (int i=0; pIdx < p.size(); i++) { - TileTypeEnum t = dev.getTile(row, col+i).getTileTypeEnum(); - if (Utils.isCLB(t)) { - upperLeft = dev.getTile(row - 4 /* TODO - Make Data Driven*/, col+i).getSites()[0]; + Tile tile = dev.getTile(row, col + i); + if (Utils.isCLB(tile.getTileTypeEnum())) { + upperLeft = tile.getSites()[0]; break; } - if (p.get(pIdx) == t) pIdx++; + if (p.get(pIdx) == tile.getTileTypeEnum()) + pIdx++; } sb.append(generatePblock(upperLeft, numSLICEColumns+numSLICEMColumns, numSLICERows)); } @@ -1136,44 +1161,42 @@ public ArrayList generatePBlockFromReport(String reportFileName, String } private int getTileRowInRegionBelow(int col, int row) { - Tile tmp = dev.getTile(row, col); + Tile refTile = null; + int targetCRColumn = dev.getNumOfClockRegionsColumns() / 2; for (int c = 0; c < dev.getColumns(); c++) { - if (Utils.isCLB(dev.getTile(row, c).getTileTypeEnum())) { - tmp = dev.getTile(row, c); + Tile tile = dev.getTile(row, c); + if (tile.getClockRegion().getColumn() == targetCRColumn && Utils.isCLB(tile.getTileTypeEnum())) { + refTile = tile; + break; } } int cleHeight = dev.getSeries().getCLEHeight(); - int sliceX = tmp.getSites()[0].getInstanceX(); - int sliceY = tmp.getSites()[0].getInstanceY() - cleHeight; + int sliceX = refTile.getSites()[0].getInstanceX(); + int sliceY = refTile.getSites()[0].getInstanceY() - cleHeight; Site tmpSite = dev.getSite("SLICE_X" + sliceX + "Y" + sliceY); - tmp = tmpSite.getTile(); + refTile = tmpSite.getTile(); if (dev.getNumOfSLRs() > 1) { - SLR master = null; - for (int i=0; i < dev.getNumOfSLRs(); i++) { - if (dev.getSLR(i).isMasterSLR()) { - master = dev.getSLR(i); - break; - } - } + SLR master = dev.getMasterSLR(); // Relocate to master SLR if necessary int slrCLBHeight = (dev.getNumOfClockRegionRows() / dev.getNumOfSLRs()) * cleHeight; // If we're below master, add - if (tmp.getRow() < master.getLowerRight().getRow()) { - while (tmp.getRow() < master.getLowerRight().getRow()) { + int lowerRightRow = master.getLowerRight().getRow(); + if (refTile.getRow() < lowerRightRow) { + while (refTile.getRow() < lowerRightRow && (tmpSite.getInstanceY() - slrCLBHeight >= 0)) { tmpSite = tmpSite.getNeighborSite(0, -slrCLBHeight); - tmp = tmpSite.getTile(); + refTile = tmpSite.getTile(); } } else { // Subtract - while (tmp.getRow() > master.getUpperLeft().getRow()) { + while (refTile.getRow() > master.getUpperLeft().getRow()) { tmpSite = tmpSite.getNeighborSite(0, slrCLBHeight); - tmp = tmpSite.getTile(); + refTile = tmpSite.getTile(); } } } - row = tmp.getRow(); + row = refTile.getRow(); return row; } diff --git a/src/com/xilinx/rapidwright/design/blocks/UtilizationType.java b/src/com/xilinx/rapidwright/design/blocks/UtilizationType.java index 32e039450..2da36b226 100644 --- a/src/com/xilinx/rapidwright/design/blocks/UtilizationType.java +++ b/src/com/xilinx/rapidwright/design/blocks/UtilizationType.java @@ -26,6 +26,14 @@ */ package com.xilinx.rapidwright.design.blocks; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.edif.EDIFHierCellInst; + /** * Different categories of fabric utilization. * @@ -39,17 +47,16 @@ public enum UtilizationType { REGS_AS_FFS("Regs as FF"), REGS_AS_LATCHES("Regs as Latch"), CARRY8S("CARRY8s"), - //F7_MUXES("F7 Muxes"), - //F8_MUXES("F8 Muxes"), - //F9_MUXES("F9 Muxes"), + F7_MUXES("F7 Muxes"), + F8_MUXES("F8 Muxes"), + F9_MUXES("F9 Muxes"), CLBS("CLBs"), CLBLS("CLBLs"), CLBMS("CLBMs"), - //LUT_FF_PAIRS("Lut/FF Pairs"), RAMB36S_FIFOS("RAMB36s/FIFOs"), RAMB18S("RAMB18s"), URAMS("URAMs"), - DSPS("DSPs"); + DSPS("DSPs"), BRAMS("BRAMS"); private String name; @@ -63,4 +70,130 @@ public String getString() { return name; } + /** + * Calculates an estimated utilization for the given design's netlist. It + * doesn't take into account placement and only depends on the design's netlist + * for an estimate. + * + * @param design The design to query. + * @return A map of utilization types and their respective counts. + */ + public static Map computeUtilization(Design design) { + Map map = new HashMap<>(); + + design.getNetlist().collapseMacroUnisims(design.getSeries()); + + Set notTracked = new HashSet<>(); + + int[] lutCounts = new int[7]; + + for (EDIFHierCellInst i : design.getNetlist().getAllLeafHierCellInstances()) { + String type = i.getCellType().getName(); + switch (type) { + case "LUT1": + lutCounts[1]++; + break; + case "LUT2": + lutCounts[2]++; + break; + case "LUT3": + lutCounts[3]++; + break; + case "LUT4": + lutCounts[4]++; + break; + case "LUT5": + lutCounts[5]++; + break; + case "LUT6": + case "LUT6_2": + case "LUT6CY": + lutCounts[6]++; + break; + case "SRL16E": + case "SRLC16E": + case "SRLC32E": + case "RAMD64E": + case "RAMS64E": + case "RAMS64E1": + case "RAMD32M64": + case "RAMS32": + case "RAMD32": + case "RAMD64E5": + case "RAM32X1S": + map.compute(LUTS_AS_MEMORY, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "RAM32X1D": + map.compute(LUTS_AS_MEMORY, (k, v) -> v == null ? 2 : (v + 2)); + break; + case "RAM32M": + case "RAM64M": + map.compute(LUTS_AS_MEMORY, (k, v) -> v == null ? 4 : (v + 4)); + break; + case "RAM64X1S": + case "RAM32M16": + map.compute(LUTS_AS_MEMORY, (k, v) -> v == null ? 8 : (v + 8)); + break; + case "FDCE": + case "FDPE": + case "FDRE": + case "FDSE": + case "AND2B1L": + map.compute(REGS_AS_FFS, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "LDCE": + case "LDPE": + map.compute(REGS_AS_LATCHES, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "CARRY8": + case "LOOKAHEAD8": + map.compute(CARRY8S, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "RAMB36E2": + case "RAMB36E5_INT": + case "FIFO36E2": + map.compute(RAMB36S_FIFOS, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "RAMB18E2": + case "RAMB18E5_INT": + map.compute(RAMB18S, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "URAM288": + case "URAM288_BASE": + case "URAM288E5": + map.compute(URAMS, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "DSP_PREADD58": + case "DSP_FP_ADDER": + case "DSP48E2": + map.compute(DSPS, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "MUXF7": + map.compute(F7_MUXES, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "MUXF8": + map.compute(F8_MUXES, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "MUXF9": + map.compute(F9_MUXES, (k, v) -> v == null ? 1 : (v + 1)); + break; + default: + if (notTracked.add(type)) + System.out.println("Didn't count: " + type); + } + } + + design.getNetlist().expandMacroUnisims(design.getSeries()); + + // Calculate potential estimates for LUTs + int lutEstimate = 0; + for (int i = 1; i < 7; i++) { + lutEstimate += i == 3 ? lutCounts[i] / 2 : lutCounts[i]; + } + map.put(CLB_LUTS, lutEstimate + map.getOrDefault(LUTS_AS_MEMORY, 0)); + + map.put(CLB_REGS, map.getOrDefault(REGS_AS_FFS, 0) + map.getOrDefault(REGS_AS_LATCHES, 0)); + map.put(BRAMS, map.getOrDefault(RAMB36S_FIFOS, 0) + (map.getOrDefault(RAMB18S, 0) / 2)); + return map; + } } diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java new file mode 100644 index 000000000..267ba7291 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java @@ -0,0 +1,325 @@ +/* + * + * 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.design.tools; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.xilinx.rapidwright.design.ClockTools; +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.Module; +import com.xilinx.rapidwright.design.ModuleInst; +import com.xilinx.rapidwright.design.Net; +import com.xilinx.rapidwright.design.NetTools; +import com.xilinx.rapidwright.design.blocks.PBlock; +import com.xilinx.rapidwright.design.blocks.PBlockGenerator; +import com.xilinx.rapidwright.design.blocks.PBlockRange; +import com.xilinx.rapidwright.device.Device; +import com.xilinx.rapidwright.device.Part; +import com.xilinx.rapidwright.device.PartNameTools; +import com.xilinx.rapidwright.device.Site; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.edif.EDIFTools; +import com.xilinx.rapidwright.util.FileTools; +import com.xilinx.rapidwright.util.MessageGenerator; +import com.xilinx.rapidwright.util.Pair; +import com.xilinx.rapidwright.util.PerformanceExplorer; +import com.xilinx.rapidwright.util.VivadoTools; + +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +/** + * A Tool to optimize, place and route a kernel and then replicate its + * implementation in an array across the fabric. + */ +public class ArrayBuilder { + + private static final List INPUT_DESIGN_OPTS = Arrays.asList("i", "input"); + private static final List INPUT_EDIF_OPTS = Arrays.asList("e", "edif"); + private static final List UTILIZATION_OPTS = Arrays.asList("u", "utilization"); + private static final List SHAPES_OPTS = Arrays.asList("s", "shapes"); + private static final List PART_OPTS = Arrays.asList("p", "part"); + private static final List PBLOCK_OPTS = Arrays.asList("b", "pblock"); + private static final List HELP_OPTS = Arrays.asList("?", "h", "help"); + private static final List TARGET_CLK_PERIOD_OPTS = Arrays.asList("c", "clk-period"); + private static final List TARGET_CLK_NAME_OPTS = Arrays.asList("n", "clk-name"); + private static final List REUSE_RESULTS_OPTS = Arrays.asList("r", "reuse"); + + private Design design; + + private List pblocks; + + private double clkPeriod; + + private String clkName; + + public static final double DEFAULT_CLK_PERIOD_TARGET = 2.0; + + private OptionParser createOptionParser() { + + OptionParser p = new OptionParser() { + { + acceptsAll(INPUT_DESIGN_OPTS, "Input Kernel Design (*.dcp or *.edf)").withRequiredArg(); + acceptsAll(PBLOCK_OPTS, "PBlock Constraint(s), separated with ';'").withRequiredArg(); + acceptsAll(INPUT_EDIF_OPTS, "Companion EDIF for DCP (*.edf)").withRequiredArg(); + acceptsAll(UTILIZATION_OPTS, "Vivado Generated Utilization Report").withRequiredArg(); + acceptsAll(SHAPES_OPTS, "Vivado Generated Shapes").withRequiredArg(); + acceptsAll(PART_OPTS, "Target AMD Part").withRequiredArg(); + acceptsAll(TARGET_CLK_PERIOD_OPTS, "Target Clock Period (ns)").withRequiredArg(); + acceptsAll(TARGET_CLK_NAME_OPTS, "Target Clock Name").withRequiredArg(); + acceptsAll(REUSE_RESULTS_OPTS, "Reuse Previous Implementation Results").withRequiredArg(); + acceptsAll(HELP_OPTS, "Print this help message").forHelp(); + } + }; + + return p; + } + + private static void printHelp(OptionParser p) { + MessageGenerator.printHeader("ArrayBuilder"); + System.out.println("Generates an optimized, implemented array of the provided kernel."); + try { + p.printHelpOn(System.out); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public Device getDevice() { + return getDesign().getDevice(); + } + + public void setDesign(Design design) { + this.design = design; + } + + public Design getDesign() { + return design; + } + + public void setPBlocks(List pblocks) { + this.pblocks = pblocks; + } + + public List getPBlocks() { + return pblocks; + } + + public void setClockPeriod(double clkPeriod) { + this.clkPeriod = clkPeriod; + } + + public double getClockPeriod() { + return clkPeriod; + } + + public void setClockName(String clkName) { + this.clkName = clkName; + } + + public String getClockName() { + return clkName; + } + + private void initializeArrayBuilder(OptionSet options) { + Path inputFile = null; + if (options.has(INPUT_DESIGN_OPTS.get(0))) { + inputFile = Paths.get((String)options.valueOf(INPUT_DESIGN_OPTS.get(0))); + if (inputFile.toString().endsWith(".dcp")) { + if (options.has(INPUT_EDIF_OPTS.get(0))) { + Path companionEDIF = Paths.get((String)options.valueOf(INPUT_EDIF_OPTS.get(0))); + setDesign(Design.readCheckpoint(inputFile, companionEDIF)); + } else { + setDesign(Design.readCheckpoint(inputFile)); + } + + } else if (inputFile.toString().endsWith(".edf")) { + EDIFNetlist netlist = EDIFTools.readEdifFile(inputFile); + if (options.has(PART_OPTS.get(0))) { + Part part = PartNameTools.getPart((String) options.valueOf(PART_OPTS.get(0))); + EDIFTools.ensureCorrectPartInEDIF(netlist, part.toString()); + } + setDesign(new Design(netlist)); + } + } else { + throw new RuntimeException("No input design found. " + + "Please specify an input kernel (*.dcp or *.edf) using options " + + INPUT_DESIGN_OPTS); + } + assert (getDesign() != null); + + if (options.has(PBLOCK_OPTS.get(0))) { + String pblockString = (String) options.valueOf(PBLOCK_OPTS.get(0)); + String[] pblockStrings = pblockString.split(";"); + List pblocks = new ArrayList(); + for (String str : pblockStrings) { + pblocks.add(new PBlock(getDevice(), str)); + } + setPBlocks(pblocks); + } else { + PBlockGenerator pb = new PBlockGenerator(); + Path utilReport = null; + Path shapesReport = null; + if (options.has(UTILIZATION_OPTS.get(0)) && options.has(SHAPES_OPTS.get(0))) { + utilReport = Paths.get((String) options.valueOf(UTILIZATION_OPTS.get(0))); + shapesReport = Paths.get((String) options.valueOf(SHAPES_OPTS.get(0))); + } else { + utilReport = Paths.get("utilization.report"); + shapesReport = Paths.get("shapes.report"); + if (!inputFile.toString().endsWith(".dcp")) { + throw new RuntimeException( + "TODO - Implement support for util/shapes file " + "generation with EDIF input file."); + } + VivadoTools.getUtilizationAndShapesReport(inputFile, utilReport, shapesReport); + } + + List pblocks = pb.generatePBlockFromReport(utilReport.toString(), shapesReport.toString()); + List pblockObjects = new ArrayList(); + for (String s : pblocks) { + PBlock pblock = new PBlock(); + for (String range : s.split(" ")) { + pblock.add(new PBlockRange(getDevice(), range)); + } + pblockObjects.add(pblock); + } + setPBlocks(pblockObjects); + } + + for (int i = 0; i < getPBlocks().size(); i++) { + System.out.println("[INFO] PBlocks Set [" + i + "]: " + getPBlocks().get(i)); + } + + if (options.has(TARGET_CLK_PERIOD_OPTS.get(0))) { + setClockPeriod(Double.parseDouble((String) options.valueOf(TARGET_CLK_PERIOD_OPTS.get(0)))); + } else { + setClockPeriod(DEFAULT_CLK_PERIOD_TARGET); + System.out.println("[INFO] No clock period set, defaulting to: " + getClockPeriod() + "ns"); + } + + if (options.has(TARGET_CLK_NAME_OPTS.get(0))) { + setClockName(((String) options.valueOf(TARGET_CLK_NAME_OPTS.get(0)))); + } else { + setClockName(ClockTools.getClockFromDesign(getDesign()).toString()); + } + + } + + public static void main(String[] args) { + ArrayBuilder ab = new ArrayBuilder(); + OptionParser p = ab.createOptionParser(); + OptionSet options = p.parse(args); + + if (options.has(HELP_OPTS.get(0))) { + printHelp(p); + return; + } + + // Load design and options + ab.initializeArrayBuilder(options); + + // Create a working directory for PerformanceExplorer + Path workDir = Paths.get("ArrayBuilder-" + FileTools.getTimeStamp().replace(" ", "-")); + boolean reuseResults = false; + if (options.has(REUSE_RESULTS_OPTS.get(0))) { + workDir = Paths.get((String) options.valueOf(REUSE_RESULTS_OPTS.get(0))); + reuseResults = true; + } + + FileTools.makeDirs(workDir.toString()); + System.out.println("[INFO] Created work directory: " + workDir.toString()); + + + // Initialize PerformanceExplorer + PerformanceExplorer pe = new PerformanceExplorer(ab.getDesign(), workDir.toString(), ab.getClockName(), + ab.getClockPeriod()); + + // Set PBlocks + Map pblocks = new HashMap<>(); + for (PBlock pb : ab.getPBlocks()) { + pblocks.put(pb, null); + } + pe.setPBlocks(pblocks); + pe.setAddEDIFAndMetadata(true); + pe.setReusePreviousResults(reuseResults); + pe.explorePerformance(); + + boolean unrouteStaticNets = false; + List> results = pe.getBestResultsPerPBlock(); + List modules = new ArrayList<>(); + for (int i = 0; i < results.size(); i++) { + Pair result = results.get(i); + Path dcpPath = result.getFirst().resolve("routed.dcp"); + if (Files.exists(dcpPath)) { + System.out.println("Reading... " + dcpPath); + Design d = Design.readCheckpoint(dcpPath); + d.setName(d.getName() + "_" + i); + Module m = new Module(d, unrouteStaticNets); + modules.add(m); + m.setPBlock(pe.getPBlock(i)); + m.calculateAllValidPlacements(d.getDevice()); + } else { + System.err.println("Missing DCP Result: " + dcpPath); + } + System.out.println(result.getFirst() + " " + result.getSecond()); + } + + Design array = new Design("array", ab.getDesign().getPartName()); + + ModuleInst curr = null; + int placed = 0; + int i = 0; + outer: for (Module module : modules) { + for (Site anchor : module.getAllValidPlacements()) { + if (curr == null) { + curr = array.createModuleInst("inst_" + i++, module); + } + if (curr.place(anchor, true, false)) { + curr = null; + placed++; + System.out.println(" ** PLACED: " + placed + " " + anchor + " " + module.getName()); + } + if (placed == 10 || placed == 13) { + break; + } + } + } + + List unrouted = NetTools.unrouteNetsWithOverlappingNodes(array); + if (unrouted.size() > 0) { + System.out.println("Found " + unrouted.size() + " overlapping nets, that were unrouted."); + } + + array.getNetlist().consolidateAllToWorkLibrary(); + array.writeCheckpoint("array.dcp"); + + } +} diff --git a/src/com/xilinx/rapidwright/device/Series.java b/src/com/xilinx/rapidwright/device/Series.java index aa6215f25..e1122c070 100644 --- a/src/com/xilinx/rapidwright/device/Series.java +++ b/src/com/xilinx/rapidwright/device/Series.java @@ -37,7 +37,7 @@ public enum Series { Series7 ("\\-7", 23, 3, 22, 17, 5, 7, 10, 0, 7, 50), UltraScale ("UltraScale$", 23, 3, -1, 17, 6, 7, 10, 0, 7, 60), UltraScalePlus("UltraScale\\+", 24, 3, -1, 18, 6, 8, 10, 0, 8, 60), - Versal ("Versal", -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + Versal ("Versal", -1, -1, -1, -1, -1, -1, -1, -1, -1, 96); private Pattern archRegex; diff --git a/src/com/xilinx/rapidwright/util/PerformanceExplorer.java b/src/com/xilinx/rapidwright/util/PerformanceExplorer.java index 35cfccfb4..22c7507c1 100644 --- a/src/com/xilinx/rapidwright/util/PerformanceExplorer.java +++ b/src/com/xilinx/rapidwright/util/PerformanceExplorer.java @@ -28,6 +28,9 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; @@ -77,6 +80,8 @@ public class PerformanceExplorer { /** Maps a pblock to optional named cell(s) to be constrained by the pblock */ private Map pblocks; + private PBlock[] pblockLookup; + private boolean containRouting; private boolean addEDIFAndMetadata; @@ -95,6 +100,8 @@ public class PerformanceExplorer { private String vivadoPath = DEFAULT_VIVADO; + private boolean reusePreviousResults; + public PerformanceExplorer(Design d, String testDir, String clkName, double targetPeriod) { init(d, testDir, clkName, targetPeriod, null); } @@ -262,6 +269,18 @@ public void setAddEDIFAndMetadata(boolean addEDIFAndMetadata) { this.addEDIFAndMetadata = addEDIFAndMetadata; } + public PBlock getPBlock(int i) { + return pblockLookup[i]; + } + + public void setReusePreviousResults(boolean value) { + this.reusePreviousResults = value; + } + + public boolean reusePreviousResults() { + return reusePreviousResults; + } + public ArrayList createTclScript(String initialDcp, String instDirectory, PlacerDirective p, RouterDirective r, String clockUncertainty, Entry pblockEntry) { PBlock pblock = pblockEntry.getKey(); @@ -274,7 +293,8 @@ public ArrayList createTclScript(String initialDcp, String instDirectory lines.add("create_pblock " + pblockName); lines.add("resize_pblock "+pblockName+" -add {"+pblock.toString()+"}"); lines.add("add_cells_to_pblock "+pblockName+" " + (pblockCells == null ? "-top" : "[get_cells {"+ pblockCells +"}]" )); - if (containRouting) { + lines.add("set_property IS_SOFT 0 [get_pblocks " + pblockName + "]"); + if (isContainRouting()) { lines.add("set_property CONTAIN_ROUTING 1 [get_pblocks "+ pblockName+"]"); } } @@ -328,6 +348,7 @@ public void explorePerformance() { int pb = 0; int jobsStarted = 0; int maxConcurrentJobs = JobQueue.isLSFAvailable() ? JobQueue.MAX_LSF_CONCURRENT_JOBS : JobQueue.MAX_LOCAL_CONCURRENT_JOBS; + pblockLookup = new PBlock[pblocks.size()]; for (Entry e : pblocks.entrySet()) { PBlock pblock = e.getKey(); for (PlacerDirective p : getPlacerDirectives()) { @@ -336,29 +357,31 @@ public void explorePerformance() { String roundedC = printNS(c); String uniqueID = p.name() + "_" + r.name() + "_" + roundedC; if (pblock != null) { - uniqueID = uniqueID + "_pblock" + pb + "_" + pblock.get(0).getLowerLeftSite() +"-"; + uniqueID = uniqueID + "_pblock" + pb + "_" + pblock.get(0).getLowerLeftSite() + "-"; } System.out.println(uniqueID); String instDir = runDirectory + File.separator + uniqueID; FileTools.makeDir(instDir); ArrayList tcl = createTclScript(dcpName, instDir, p, r, roundedC, e); String scriptName = instDir + File.separator + RUN_TCL_NAME; - FileTools.writeLinesToTextFile(tcl, scriptName); - - Job j = JobQueue.createJob(); - j.setRunDir(instDir); - j.setCommand(getVivadoPath() + " -mode batch -source " + scriptName); - if (jobsStarted < maxConcurrentJobs) { - j.launchJob(); - jobs.addRunningJob(j); - jobsStarted++; - } else { - jobs.addJob(j); + + if (!reusePreviousResults) { + FileTools.writeLinesToTextFile(tcl, scriptName); + Job j = JobQueue.createJob(); + j.setRunDir(instDir); + j.setCommand(getVivadoPath() + " -mode batch -source " + scriptName); + if (jobsStarted < maxConcurrentJobs) { + j.launchJob(); + jobs.addRunningJob(j); + jobsStarted++; + } else { + jobs.addJob(j); + } } } } } - pb++; + pblockLookup[pb++] = pblock; } boolean success = jobs.runAllToCompletion(maxConcurrentJobs); @@ -366,6 +389,54 @@ public void explorePerformance() { System.out.println("Performance Explorer " + (success ? "Finished Successfully." : "Failed!")); } + private Float parseWNSFromTimingReport(Path timingReport) { + for (String line : FileTools.getLinesFromTextFile(timingReport.toString())) { + if (line.contains("Slack ")) { + String[] values = line.split("\\s+"); + assert (values.length >= 4); + String value = values[3].replace("ns", ""); + return Float.parseFloat(value); + } + } + return null; + } + + private static final String PBLOCK_SUFFIX = "_pblock"; + + public List> getBestResultsPerPBlock() { + List> results = new ArrayList<>(); + for (int i = 0; i < pblocks.size(); i++) { + results.add(new Pair(null, -1 * Float.MAX_VALUE)); + } + Path runDir = Paths.get(runDirectory); + String[] runDirFiles = runDir.toFile().list(); + for (String dirName : runDirFiles) { + Path dir = runDir.resolve(dirName); + int pblockIdx = 0; + int i = dirName.indexOf(PBLOCK_SUFFIX); + if (i >= 0) { + int start = i + PBLOCK_SUFFIX.length(); + int end = dirName.indexOf('_', start); + String tmp = dirName.substring(start, end); + pblockIdx = Integer.parseInt(tmp); + } + if (Files.isDirectory(dir)) { + Path routeTiming = dir.resolve("route_timing.twr"); + if (Files.exists(routeTiming)) { + Float value = parseWNSFromTimingReport(routeTiming); + Pair bestFoundSoFar = results.get(pblockIdx); + if (value > bestFoundSoFar.getSecond()) { + bestFoundSoFar.setFirst(dir); + bestFoundSoFar.setSecond(value); + } + Pair curr = results.get(pblockIdx); + curr.setFirst(dir); + curr.setSecond(value); + } + } + } + return results; + } private static final String INPUT_DCP_OPT = "i"; private static final String PBLOCK_FILE_OPT = "b"; diff --git a/src/com/xilinx/rapidwright/util/VivadoTools.java b/src/com/xilinx/rapidwright/util/VivadoTools.java index 50bfa4e9f..f6edf22ed 100644 --- a/src/com/xilinx/rapidwright/util/VivadoTools.java +++ b/src/com/xilinx/rapidwright/util/VivadoTools.java @@ -161,15 +161,25 @@ public static String reportRouteStatus(Design design, String netName) { } private static Path writeCheckpoint(Design design) { - final Path workdir = FileSystems.getDefault() - .getPath("vivadoToolsWorkdir" + FileTools.getUniqueProcessAndHostID()); - File workdirHandle = new File(workdir.toString()); - workdirHandle.mkdirs(); + final Path workdir = createTempVivadoToolsWorkDir(); final Path dcp = workdir.resolve("checkpoint.dcp"); design.writeCheckpoint(dcp); return dcp; } + /** + * Creates a unique, temporary work directory for Vivado interaction. + * + * @return + */ + public static Path createTempVivadoToolsWorkDir() { + Path workDir = FileSystems.getDefault().getPath("vivadoToolsWorkdir" + FileTools.getUniqueProcessAndHostID()); + ; + File workDirHandle = new File(workDir.toString()); + workDirHandle.mkdirs(); + return workDir; + } + /** * Run Vivado's `write_bitstream` on the provided DCP file to generate a bit * file at the specified location. @@ -184,11 +194,7 @@ private static Path writeCheckpoint(Design design) { * @return The output of Vivado as a list of Strings */ public static List writeBitstream(Path dcp, Path bitFile, boolean hasEncryptedIP, String tclPremable) { - final Path workdir = FileSystems.getDefault() - .getPath("vivadoToolsWorkdir" + FileTools.getUniqueProcessAndHostID()); - File workdirHandle = new File(workdir.toString()); - workdirHandle.mkdirs(); - + final Path workdir = createTempVivadoToolsWorkDir(); final Path outputLog = workdir.resolve("outputLog.log"); StringBuilder sb = new StringBuilder(); sb.append(createTclDCPLoadCommand(dcp, hasEncryptedIP)); @@ -256,15 +262,9 @@ public static ReportRouteStatusResult reportRouteStatus(Path dcp) { * @return ReportRouteStatusResult object. */ public static ReportRouteStatusResult reportRouteStatus(Path dcp, boolean encrypted) { - final Path workdir = FileSystems.getDefault() - .getPath("vivadoToolsWorkdir" + FileTools.getUniqueProcessAndHostID()); - File workdirHandle = new File(workdir.toString()); - workdirHandle.mkdirs(); - + final Path workdir = createTempVivadoToolsWorkDir(); ReportRouteStatusResult rrs = reportRouteStatus(dcp, workdir, encrypted); - FileTools.deleteFolder(workdir.toString()); - return rrs; } @@ -408,4 +408,29 @@ public static float getWorstSetupSlack(Path dcp, Path workdir, boolean encrypted } return Float.NaN; } + + /** + * Creates a utilization report and shapes report for a given synthesized DCP + * file. + * + * @param design The input DCP to measure and extract reports from. + * @param utilReport The desired path to a utilization report from Vivado + * (report_utilization -file ). + * @param shapesReport The desired shapes report. + */ + public static void getUtilizationAndShapesReport(Path design, Path utilReport, Path shapesReport) { + final Path workDir = createTempVivadoToolsWorkDir(); + final Path outputLog = workDir.resolve("outputLog.log"); + + StringBuilder sb = new StringBuilder(); + sb.append("open_checkpoint " + design.toString() + "\n"); + sb.append("report_utilization -file " + utilReport.toString() + "\n"); + sb.append("set_param place.debugShape " + shapesReport.toString() + "\n"); + sb.append("place_design -directive Quick \n"); + sb.append("set_param place.debugShape \"\"\n"); + + VivadoTools.runTcl(outputLog, sb.toString(), true); + + FileTools.deleteFolder(workDir.toString()); + } } diff --git a/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java b/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java new file mode 100644 index 000000000..4d9492e1a --- /dev/null +++ b/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java @@ -0,0 +1,30 @@ +package com.xilinx.rapidwright.design.blocks; + +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.support.RapidWrightDCP; + +public class TestUtilizationType { + + @Test + public void testComputeUtilization() { + Design design = RapidWrightDCP.loadDCP("optical-flow.dcp"); + + Map utilization = UtilizationType.computeUtilization(design); + System.out.println(utilization); + + Assertions.assertEquals(2891, utilization.get(UtilizationType.CARRY8S)); + Assertions.assertEquals(25740, utilization.get(UtilizationType.CLB_REGS)); + Assertions.assertEquals(124, utilization.get(UtilizationType.DSPS)); + Assertions.assertEquals(59, utilization.get(UtilizationType.RAMB36S_FIFOS)); + Assertions.assertEquals(10, utilization.get(UtilizationType.RAMB18S)); + Assertions.assertEquals(287, utilization.get(UtilizationType.LUTS_AS_MEMORY)); + Assertions.assertEquals(64, utilization.get(UtilizationType.BRAMS)); + Assertions.assertEquals(30239, utilization.get(UtilizationType.CLB_LUTS)); + + } +} From b7bcbdd3a1da17303db1e818ae5bad5c7aa6efaf Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 10 Apr 2025 13:29:30 -0700 Subject: [PATCH 03/24] Add missing header, import Signed-off-by: Chris Lavin --- .../design/blocks/PBlockGenerator.java | 1 + .../design/blocks/TestUtilizationType.java | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java b/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java index e7ceb8a99..8899996be 100644 --- a/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java +++ b/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java @@ -46,6 +46,7 @@ import com.xilinx.rapidwright.device.FamilyType; import com.xilinx.rapidwright.device.Part; import com.xilinx.rapidwright.device.PartNameTools; +import com.xilinx.rapidwright.device.SLR; import com.xilinx.rapidwright.device.Series; import com.xilinx.rapidwright.device.Site; import com.xilinx.rapidwright.device.SiteTypeEnum; diff --git a/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java b/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java index 4d9492e1a..ac163d0ea 100644 --- a/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java +++ b/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java @@ -1,3 +1,26 @@ +/* + * + * 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.design.blocks; import java.util.Map; From e6695b664af3d1a53ef3dc68830b73cd086c7f68 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Mon, 28 Apr 2025 13:28:18 -0700 Subject: [PATCH 04/24] Adds a skip implementation option Signed-off-by: Chris Lavin --- .../design/tools/ArrayBuilder.java | 187 +++++++++++++----- src/com/xilinx/rapidwright/util/Utils.java | 3 +- 2 files changed, 144 insertions(+), 46 deletions(-) diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java index 267ba7291..023737b6d 100644 --- a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java @@ -29,16 +29,19 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import com.xilinx.rapidwright.design.Cell; import com.xilinx.rapidwright.design.ClockTools; import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.Module; import com.xilinx.rapidwright.design.ModuleInst; import com.xilinx.rapidwright.design.Net; import com.xilinx.rapidwright.design.NetTools; +import com.xilinx.rapidwright.design.SiteInst; import com.xilinx.rapidwright.design.blocks.PBlock; import com.xilinx.rapidwright.design.blocks.PBlockGenerator; import com.xilinx.rapidwright.design.blocks.PBlockRange; @@ -46,8 +49,12 @@ import com.xilinx.rapidwright.device.Part; import com.xilinx.rapidwright.device.PartNameTools; import com.xilinx.rapidwright.device.Site; +import com.xilinx.rapidwright.edif.EDIFCellInst; +import com.xilinx.rapidwright.edif.EDIFNet; import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.edif.EDIFPortInst; import com.xilinx.rapidwright.edif.EDIFTools; +import com.xilinx.rapidwright.tests.CodePerfTracker; import com.xilinx.rapidwright.util.FileTools; import com.xilinx.rapidwright.util.MessageGenerator; import com.xilinx.rapidwright.util.Pair; @@ -64,6 +71,7 @@ public class ArrayBuilder { private static final List INPUT_DESIGN_OPTS = Arrays.asList("i", "input"); + private static final List OUTPUT_DESIGN_OPTS = Arrays.asList("o", "output"); private static final List INPUT_EDIF_OPTS = Arrays.asList("e", "edif"); private static final List UTILIZATION_OPTS = Arrays.asList("u", "utilization"); private static final List SHAPES_OPTS = Arrays.asList("s", "shapes"); @@ -73,6 +81,7 @@ public class ArrayBuilder { private static final List TARGET_CLK_PERIOD_OPTS = Arrays.asList("c", "clk-period"); private static final List TARGET_CLK_NAME_OPTS = Arrays.asList("n", "clk-name"); private static final List REUSE_RESULTS_OPTS = Arrays.asList("r", "reuse"); + private static final List SKIP_IMPL_OPTS = Arrays.asList("k", "skip-impl"); private Design design; @@ -82,6 +91,12 @@ public class ArrayBuilder { private String clkName; + private boolean skipImpl; + + private String outputName; + + private CodePerfTracker t; + public static final double DEFAULT_CLK_PERIOD_TARGET = 2.0; private OptionParser createOptionParser() { @@ -89,6 +104,7 @@ private OptionParser createOptionParser() { OptionParser p = new OptionParser() { { acceptsAll(INPUT_DESIGN_OPTS, "Input Kernel Design (*.dcp or *.edf)").withRequiredArg(); + acceptsAll(OUTPUT_DESIGN_OPTS, "Output Array Design (default is 'array.dcp')").withRequiredArg(); acceptsAll(PBLOCK_OPTS, "PBlock Constraint(s), separated with ';'").withRequiredArg(); acceptsAll(INPUT_EDIF_OPTS, "Companion EDIF for DCP (*.edf)").withRequiredArg(); acceptsAll(UTILIZATION_OPTS, "Vivado Generated Utilization Report").withRequiredArg(); @@ -96,7 +112,8 @@ private OptionParser createOptionParser() { acceptsAll(PART_OPTS, "Target AMD Part").withRequiredArg(); acceptsAll(TARGET_CLK_PERIOD_OPTS, "Target Clock Period (ns)").withRequiredArg(); acceptsAll(TARGET_CLK_NAME_OPTS, "Target Clock Name").withRequiredArg(); - acceptsAll(REUSE_RESULTS_OPTS, "Reuse Previous Implementation Results").withRequiredArg(); + acceptsAll(REUSE_RESULTS_OPTS, "Reuse Previous Implementation Results"); + acceptsAll(SKIP_IMPL_OPTS, "Skip Implementation of the Kernel"); acceptsAll(HELP_OPTS, "Print this help message").forHelp(); } }; @@ -104,6 +121,14 @@ private OptionParser createOptionParser() { return p; } + public ArrayBuilder() { + + } + + public ArrayBuilder(CodePerfTracker t) { + this.t = t; + } + private static void printHelp(OptionParser p) { MessageGenerator.printHeader("ArrayBuilder"); System.out.println("Generates an optimized, implemented array of the provided kernel."); @@ -131,7 +156,7 @@ public void setPBlocks(List pblocks) { } public List getPBlocks() { - return pblocks; + return pblocks == null ? Collections.emptyList() : pblocks; } public void setClockPeriod(double clkPeriod) { @@ -150,16 +175,43 @@ public String getClockName() { return clkName; } + public boolean isSkipImpl() { + return skipImpl; + } + + public void setSkipImpl(boolean skipImpl) { + this.skipImpl = skipImpl; + } + + /** + * @return the outputName + */ + public String getOutputName() { + return outputName; + } + + /** + * @param outputName the outputName to set + */ + public void setOutputName(String outputName) { + this.outputName = outputName; + } + private void initializeArrayBuilder(OptionSet options) { Path inputFile = null; + + if (options.has(SKIP_IMPL_OPTS.get(0))) { + this.skipImpl = true; + } + if (options.has(INPUT_DESIGN_OPTS.get(0))) { inputFile = Paths.get((String)options.valueOf(INPUT_DESIGN_OPTS.get(0))); if (inputFile.toString().endsWith(".dcp")) { if (options.has(INPUT_EDIF_OPTS.get(0))) { Path companionEDIF = Paths.get((String)options.valueOf(INPUT_EDIF_OPTS.get(0))); - setDesign(Design.readCheckpoint(inputFile, companionEDIF)); + setDesign(Design.readCheckpoint(inputFile, companionEDIF, CodePerfTracker.SILENT)); } else { - setDesign(Design.readCheckpoint(inputFile)); + setDesign(Design.readCheckpoint(inputFile, CodePerfTracker.SILENT)); } } else if (inputFile.toString().endsWith(".edf")) { @@ -185,7 +237,7 @@ private void initializeArrayBuilder(OptionSet options) { pblocks.add(new PBlock(getDevice(), str)); } setPBlocks(pblocks); - } else { + } else if (!skipImpl) { PBlockGenerator pb = new PBlockGenerator(); Path utilReport = null; Path shapesReport = null; @@ -233,8 +285,43 @@ private void initializeArrayBuilder(OptionSet options) { } + public static void removeBUFGs(Design design) { + // Find BUFGs in the design and remove them + List bufgs = new ArrayList<>(); + for (Cell c : design.getCells()) { + if (c.getType().equals("BUFG") || c.getType().equals("BUFGCE")) { + bufgs.add(c); + } + } + + for (Cell bufg : bufgs) { + SiteInst si = bufg.getSiteInst(); + String inputSiteWire = bufg.getSiteWireNameFromLogicalPin("I"); + Net input = si.getNetFromSiteWire(inputSiteWire); + String outputSiteWire = bufg.getSiteWireNameFromLogicalPin("O"); + Net output = si.getNetFromSiteWire(outputSiteWire); + + // Remove BUFG + design.removeCell(bufg); + + design.removeSiteInst(bufg.getSiteInst()); + EDIFCellInst bufgInst = design.getTopEDIFCell().removeCellInst(bufg.getName()); + for (EDIFPortInst portInst : bufgInst.getPortInsts()) { + portInst.getNet().removePortInst(portInst); + } + EDIFNet clkin = design.getTopEDIFCell().getNet(input.getName()); + EDIFNet clk = design.getTopEDIFCell().getNet(output.getName()); + for (EDIFPortInst portInst : clkin.getPortInsts()) { + clk.addPortInst(portInst); + } + design.getTopEDIFCell().removeNet(clkin); + } + } + public static void main(String[] args) { - ArrayBuilder ab = new ArrayBuilder(); + CodePerfTracker t = new CodePerfTracker(ArrayBuilder.class.getName()); + t.start("Init"); + ArrayBuilder ab = new ArrayBuilder(t); OptionParser p = ab.createOptionParser(); OptionSet options = p.parse(args); @@ -253,44 +340,56 @@ public static void main(String[] args) { workDir = Paths.get((String) options.valueOf(REUSE_RESULTS_OPTS.get(0))); reuseResults = true; } + t.stop().start("Implement Kernel"); - FileTools.makeDirs(workDir.toString()); - System.out.println("[INFO] Created work directory: " + workDir.toString()); - - - // Initialize PerformanceExplorer - PerformanceExplorer pe = new PerformanceExplorer(ab.getDesign(), workDir.toString(), ab.getClockName(), - ab.getClockPeriod()); - - // Set PBlocks - Map pblocks = new HashMap<>(); - for (PBlock pb : ab.getPBlocks()) { - pblocks.put(pb, null); - } - pe.setPBlocks(pblocks); - pe.setAddEDIFAndMetadata(true); - pe.setReusePreviousResults(reuseResults); - pe.explorePerformance(); - - boolean unrouteStaticNets = false; - List> results = pe.getBestResultsPerPBlock(); List modules = new ArrayList<>(); - for (int i = 0; i < results.size(); i++) { - Pair result = results.get(i); - Path dcpPath = result.getFirst().resolve("routed.dcp"); - if (Files.exists(dcpPath)) { - System.out.println("Reading... " + dcpPath); - Design d = Design.readCheckpoint(dcpPath); - d.setName(d.getName() + "_" + i); - Module m = new Module(d, unrouteStaticNets); - modules.add(m); - m.setPBlock(pe.getPBlock(i)); - m.calculateAllValidPlacements(d.getDevice()); - } else { - System.err.println("Missing DCP Result: " + dcpPath); + boolean unrouteStaticNets = false; + if (!ab.isSkipImpl()) { + FileTools.makeDirs(workDir.toString()); + System.out.println("[INFO] Created work directory: " + workDir.toString()); + + // Initialize PerformanceExplorer + PerformanceExplorer pe = new PerformanceExplorer(ab.getDesign(), workDir.toString(), + ab.getClockName(), ab.getClockPeriod()); + + // Set PBlocks + Map pblocks = new HashMap<>(); + for (PBlock pb : ab.getPBlocks()) { + pblocks.put(pb, null); } - System.out.println(result.getFirst() + " " + result.getSecond()); + pe.setPBlocks(pblocks); + pe.setAddEDIFAndMetadata(true); + pe.setReusePreviousResults(reuseResults); + pe.explorePerformance(); + + List> results = pe.getBestResultsPerPBlock(); + for (int i = 0; i < results.size(); i++) { + Pair result = results.get(i); + Path dcpPath = result.getFirst().resolve("routed.dcp"); + if (Files.exists(dcpPath)) { + System.out.println("Reading... " + dcpPath); + Design d = Design.readCheckpoint(dcpPath); + d.setName(d.getName() + "_" + i); + Module m = new Module(d, unrouteStaticNets); + modules.add(m); + m.setPBlock(pe.getPBlock(i)); + m.calculateAllValidPlacements(d.getDevice()); + } else { + System.err.println("Missing DCP Result: " + dcpPath); + } + System.out.println(result.getFirst() + " " + result.getSecond()); + } + } else /* skipImpl==true */ { + // Just use the design we loaded and replicate it + removeBUFGs(ab.getDesign()); + Module m = new Module(ab.getDesign(), unrouteStaticNets); + m.calculateAllValidPlacements(ab.getDevice()); + if (ab.getPBlocks().size() > 0) { + m.setPBlock(ab.getPBlocks().get(0)); + } + modules.add(m); } + t.stop().start("Place Instances"); Design array = new Design("array", ab.getDesign().getPartName()); @@ -307,9 +406,6 @@ public static void main(String[] args) { placed++; System.out.println(" ** PLACED: " + placed + " " + anchor + " " + module.getName()); } - if (placed == 10 || placed == 13) { - break; - } } } @@ -319,7 +415,8 @@ public static void main(String[] args) { } array.getNetlist().consolidateAllToWorkLibrary(); - array.writeCheckpoint("array.dcp"); - + t.stop().start("Write DCP"); + array.writeCheckpoint("array.dcp", CodePerfTracker.SILENT); + t.stop().printSummary(); } } diff --git a/src/com/xilinx/rapidwright/util/Utils.java b/src/com/xilinx/rapidwright/util/Utils.java index 8a1950898..96b2048d1 100644 --- a/src/com/xilinx/rapidwright/util/Utils.java +++ b/src/com/xilinx/rapidwright/util/Utils.java @@ -359,7 +359,8 @@ public static boolean isIOB(SiteTypeEnum s) { lockedSiteTypes = EnumSet.of( SiteTypeEnum.CONFIG_SITE, - SiteTypeEnum.BUFG + SiteTypeEnum.BUFG, + SiteTypeEnum.BUFGCE ); sliceTypes = EnumSet.of( From 8a42860391fc5eb7fa60567e01da10353edf169a Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 8 May 2025 08:07:28 -0700 Subject: [PATCH 05/24] Avoids NPEs Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/design/DesignTools.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/com/xilinx/rapidwright/design/DesignTools.java b/src/com/xilinx/rapidwright/design/DesignTools.java index 021b05af8..e1e3e0c52 100644 --- a/src/com/xilinx/rapidwright/design/DesignTools.java +++ b/src/com/xilinx/rapidwright/design/DesignTools.java @@ -1796,7 +1796,7 @@ public static void makeBlackBox(Design d, EDIFHierCellInst hierarchicalCell) { SiteInst si = c.getSiteInst(); // Check for VCC on A6 and remove if needed - if (c.getBEL().isLUT() && c.getBELName().endsWith("5LUT")) { + if (bel != null && bel.isLUT() && bel.getName().endsWith("5LUT")) { SitePinInst vcc = c.getSiteInst().getSitePinInst(c.getBELName().charAt(0) + "6"); if (vcc != null && vcc.getNet().getName().equals(Net.VCC_NET)) { boolean hasOtherSink = false; @@ -1823,11 +1823,12 @@ public static void makeBlackBox(Design d, EDIFHierCellInst hierarchicalCell) { pinsToRemove.computeIfAbsent(pin.getNet(), $ -> new HashSet<>()).add(pin); } } - touched.add(c.getSiteInst()); + if (si != null) { + touched.add(si); + } c.unplace(); d.removeCell(c.getName()); - si.removeCell(bel); } t.stop().start("cleanup t-prims"); From b311226b9bc679bd20ab89fb1a39c478a361a7ea Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 13 May 2025 14:43:25 -0700 Subject: [PATCH 06/24] Options for array construction Signed-off-by: Chris Lavin --- .../design/tools/ArrayBuilder.java | 171 +++++++++++++++++- .../rapidwright/util/PerformanceExplorer.java | 30 ++- 2 files changed, 182 insertions(+), 19 deletions(-) diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java index 023737b6d..f9a902b29 100644 --- a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java @@ -41,19 +41,30 @@ import com.xilinx.rapidwright.design.ModuleInst; import com.xilinx.rapidwright.design.Net; import com.xilinx.rapidwright.design.NetTools; +import com.xilinx.rapidwright.design.NetType; import com.xilinx.rapidwright.design.SiteInst; +import com.xilinx.rapidwright.design.SitePinInst; +import com.xilinx.rapidwright.design.Unisim; import com.xilinx.rapidwright.design.blocks.PBlock; import com.xilinx.rapidwright.design.blocks.PBlockGenerator; import com.xilinx.rapidwright.design.blocks.PBlockRange; +import com.xilinx.rapidwright.device.BEL; +import com.xilinx.rapidwright.device.ClockRegion; import com.xilinx.rapidwright.device.Device; import com.xilinx.rapidwright.device.Part; import com.xilinx.rapidwright.device.PartNameTools; +import com.xilinx.rapidwright.device.Series; import com.xilinx.rapidwright.device.Site; +import com.xilinx.rapidwright.device.Tile; +import com.xilinx.rapidwright.edif.EDIFCell; import com.xilinx.rapidwright.edif.EDIFCellInst; +import com.xilinx.rapidwright.edif.EDIFDirection; import com.xilinx.rapidwright.edif.EDIFNet; import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.edif.EDIFPort; import com.xilinx.rapidwright.edif.EDIFPortInst; import com.xilinx.rapidwright.edif.EDIFTools; +import com.xilinx.rapidwright.edif.EDIFValueType; import com.xilinx.rapidwright.tests.CodePerfTracker; import com.xilinx.rapidwright.util.FileTools; import com.xilinx.rapidwright.util.MessageGenerator; @@ -82,6 +93,7 @@ public class ArrayBuilder { private static final List TARGET_CLK_NAME_OPTS = Arrays.asList("n", "clk-name"); private static final List REUSE_RESULTS_OPTS = Arrays.asList("r", "reuse"); private static final List SKIP_IMPL_OPTS = Arrays.asList("k", "skip-impl"); + private static final List LIMIT_INSTS_OPTS = Arrays.asList("l", "limit-inst-count"); private Design design; @@ -97,6 +109,8 @@ public class ArrayBuilder { private CodePerfTracker t; + private int instCountLimit = Integer.MAX_VALUE; + public static final double DEFAULT_CLK_PERIOD_TARGET = 2.0; private OptionParser createOptionParser() { @@ -114,6 +128,7 @@ private OptionParser createOptionParser() { acceptsAll(TARGET_CLK_NAME_OPTS, "Target Clock Name").withRequiredArg(); acceptsAll(REUSE_RESULTS_OPTS, "Reuse Previous Implementation Results"); acceptsAll(SKIP_IMPL_OPTS, "Skip Implementation of the Kernel"); + acceptsAll(LIMIT_INSTS_OPTS, "Limit number of instance copies").withRequiredArg(); acceptsAll(HELP_OPTS, "Print this help message").forHelp(); } }; @@ -183,20 +198,22 @@ public void setSkipImpl(boolean skipImpl) { this.skipImpl = skipImpl; } - /** - * @return the outputName - */ public String getOutputName() { return outputName; } - /** - * @param outputName the outputName to set - */ public void setOutputName(String outputName) { this.outputName = outputName; } + public int getInstCountLimit() { + return instCountLimit; + } + + public void setInstCountLimit(int instCountLimit) { + this.instCountLimit = instCountLimit; + } + private void initializeArrayBuilder(OptionSet options) { Path inputFile = null; @@ -283,6 +300,16 @@ private void initializeArrayBuilder(OptionSet options) { setClockName(ClockTools.getClockFromDesign(getDesign()).toString()); } + if (options.has(OUTPUT_DESIGN_OPTS.get(0))) { + setOutputName((String) options.valueOf(OUTPUT_DESIGN_OPTS.get(0))); + } else { + setOutputName("array.dcp"); + } + + if (options.has(LIMIT_INSTS_OPTS.get(0))) { + setInstCountLimit(Integer.parseInt((String) options.valueOf(LIMIT_INSTS_OPTS.get(0)))); + } + } public static void removeBUFGs(Design design) { @@ -402,21 +429,147 @@ public static void main(String[] args) { curr = array.createModuleInst("inst_" + i++, module); } if (curr.place(anchor, true, false)) { - curr = null; + if (straddlesClockRegion(curr)) { + curr.unplace(); + continue; + } placed++; - System.out.println(" ** PLACED: " + placed + " " + anchor + " " + module.getName()); + System.out.println(" ** PLACED: " + placed + " " + anchor + " " + curr.getName()); + curr = null; + if (placed >= ab.getInstCountLimit()) { + break outer; + } } } } + List unrouted = NetTools.unrouteNetsWithOverlappingNodes(array); if (unrouted.size() > 0) { System.out.println("Found " + unrouted.size() + " overlapping nets, that were unrouted."); } + if (ab.isSkipImpl()) { + EDIFCell top = array.getTopEDIFCell(); + if (array.getNet(ab.getClockName()) == null) { + // Create BUFG and clock net, then connect to all instances + Cell bufg = createBUFGCE(array, top, "bufg", array.getDevice().getSite("BUFGCE_X2Y0")); + Net clk = array.createNet(ab.getClockName()); + clk.connect(bufg, "O"); + Net clkIn = array.createNet(ab.getClockName() + "_in"); + clkIn.connect(bufg, "I"); + EDIFPort clkInPort = top.createPort(ab.getClockName(), EDIFDirection.INPUT, 1); + clkIn.getLogicalNet().createPortInst(clkInPort); + EDIFNet logClkNet = clk.getLogicalNet(); + for (EDIFCellInst inst : top.getCellInsts()) { + EDIFPort port = inst.getPort(ab.getClockName()); + if (port != null) { + logClkNet.createPortInst(port, inst); + } + } + } + + // Port up unconnected inputs + for (EDIFPort topPort : modules.get(0).getNetlist().getTopCell().getPorts()) { + if (topPort.isInput()) { + if (top.getPort(topPort.getName()) == null) { + EDIFPort port = top.createPort(topPort); + if (port.isBus()) { + for (int j = 0; j < port.getWidth(); j++) { + EDIFNet net = top.createNet(port.getPortInstNameFromPort(j)); + net.createPortInst(port, j); + for (ModuleInst mi : array.getModuleInsts()) { + net.createPortInst(port, j, mi.getCellInst()); + } + } + } else { + EDIFNet net = top.createNet(port.getName()); + net.createPortInst(port); + for (ModuleInst mi : array.getModuleInsts()) { + net.createPortInst(port, mi.getCellInst()); + } + } + } + } + } + + PerformanceExplorer.updateClockPeriodConstraint(array, ab.getClockName(), ab.getClockPeriod()); + array.setDesignOutOfContext(true); + array.setAutoIOBuffers(false); + } + array.getNetlist().consolidateAllToWorkLibrary(); t.stop().start("Write DCP"); - array.writeCheckpoint("array.dcp", CodePerfTracker.SILENT); + array.writeCheckpoint(ab.getOutputName(), CodePerfTracker.SILENT); t.stop().printSummary(); } + + public static Cell createBUFGCE(Design design, EDIFCell parent, String name, Site location) { + Cell bufgce = design.createAndPlaceCell(parent, name, Unisim.BUFGCE, location, location.getBEL("BUFCE")); + + bufgce.addProperty("CE_TYPE", "ASYNC", EDIFValueType.STRING); + + // Ensure a VCC cell source in the current cell + EDIFTools.getStaticNet(NetType.VCC, parent, design.getNetlist()); + + bufgce.getSiteInst().addSitePIP("CEINV", "CE_PREINV"); + bufgce.getSiteInst().addSitePIP("IINV", "I_PREINV"); + + if (design.getSeries() == Series.Versal) { + BEL ceinv = bufgce.getSite().getBEL("CEINV"); + bufgce.getSiteInst().routeIntraSiteNet(design.getVccNet(), ceinv.getPin("CE"), ceinv.getPin("CE_PREINV")); + design.getVccNet().addPin(new SitePinInst(false, "CE", bufgce.getSiteInst())); + } else if (design.getSeries() == Series.UltraScalePlus) { + // TODO + } + // Remove CE:VCC entry for CE:CE + bufgce.removePinMapping("CE"); + bufgce.addPinMapping("CE", "CE"); + + return bufgce; + } + + private static boolean straddlesClockRegion(ModuleInst mi) { + ClockRegion cr = mi.getAnchor().getSite().getClockRegion(); + for (SiteInst si : mi.getSiteInsts()) { + if (si.getSite().getClockRegion() != cr) { + return true; + } + } + return false; + } + + private static boolean straddlesClockRegionOrRCLK(ModuleInst mi) { + ClockRegion cr = mi.getAnchor().getSite().getClockRegion(); + int centerRow = getRCLKRowIndex(cr); + boolean inTop = false; + boolean inBot = false; + for (SiteInst si : mi.getSiteInsts()) { + inTop |= si.getTile().getRow() > centerRow; + inBot |= si.getTile().getRow() < centerRow; + if ((inTop && inBot) || si.getSite().getClockRegion() != cr) { + return true; + } + } + return false; + } + + private static int getRCLKRowIndex(ClockRegion cr) { + Tile center = cr.getApproximateCenter(); + int searchGridDim = 0; + outer: while (!center.getName().startsWith("RCLK_")) { + searchGridDim++; + for (int row = -searchGridDim; row < searchGridDim; row++) { + for (int col = -searchGridDim; col < searchGridDim; col++) { + Tile neighbor = center.getTileNeighbor(col, row); + if (neighbor != null) { + neighbor.getName().startsWith("RCLK_"); + center = neighbor; + break outer; + } + } + } + } + return center.getRow(); + } } diff --git a/src/com/xilinx/rapidwright/util/PerformanceExplorer.java b/src/com/xilinx/rapidwright/util/PerformanceExplorer.java index 22c7507c1..8ddebc66a 100644 --- a/src/com/xilinx/rapidwright/util/PerformanceExplorer.java +++ b/src/com/xilinx/rapidwright/util/PerformanceExplorer.java @@ -316,6 +316,25 @@ public ArrayList createTclScript(String initialDcp, String instDirectory return lines; } + public static void updateClockPeriodConstraint(Design design, String clkName, double period) { + // Update clock period constraint + boolean foundExistingConstraint = false; + for (ConstraintGroup g : ConstraintGroup.values()) { + List xdcList = design.getXDCConstraints(g); + for (int i = 0; i < xdcList.size(); i++) { + String xdc = xdcList.get(i); + if (xdc.contains("create_clock") + && (xdc.contains("-name " + clkName) || xdc.contains("[get_ports " + clkName + "]"))) { + // TODO This may overwrite other existing options + xdcList.set(i, "create_clock -period " + period + " [get_ports " + clkName + "]"); + foundExistingConstraint = true; + } + } + } + if (!foundExistingConstraint) { + design.addXDCConstraint("create_clock -period " + period + " [get_ports " + clkName + "]"); + } + } public void explorePerformance() { if (vivadoPath.equals(DEFAULT_VIVADO) && !FileTools.isVivadoOnPath()) { @@ -326,16 +345,7 @@ public void explorePerformance() { FileTools.makeDirs(runDirectory); runDirectory = new File(runDirectory).getAbsolutePath(); String dcpName = runDirectory + File.separator + INITIAL_DCP_NAME; - // Update clock period constraint - for (ConstraintGroup g : ConstraintGroup.values()) { - List xdcList = design.getXDCConstraints(g); - for (int i=0; i < xdcList.size(); i++) { - String xdc = xdcList.get(i); - if (xdc.contains("create_clock") && xdc.contains("-name " + clkName)) { - // TODO - For now, user will need to update DCP beforehand - } - } - } + updateClockPeriodConstraint(design, getClkName(), getTargetPeriod()); design.writeCheckpoint(dcpName); JobQueue jobs = new JobQueue(); From 3feb9125ad92b06bee7b9d8f17c8da72471a54af Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Wed, 21 May 2025 14:22:18 -0700 Subject: [PATCH 07/24] Adding ArrayBuilder to main entrypoint Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/MainEntrypoint.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/com/xilinx/rapidwright/MainEntrypoint.java b/src/com/xilinx/rapidwright/MainEntrypoint.java index ee8b519bc..4716632fc 100644 --- a/src/com/xilinx/rapidwright/MainEntrypoint.java +++ b/src/com/xilinx/rapidwright/MainEntrypoint.java @@ -37,6 +37,7 @@ import com.xilinx.rapidwright.design.blocks.PBlock; import com.xilinx.rapidwright.design.blocks.PBlockGenerator; import com.xilinx.rapidwright.design.merge.MergeDesigns; +import com.xilinx.rapidwright.design.tools.ArrayBuilder; import com.xilinx.rapidwright.design.tools.LUTTools; import com.xilinx.rapidwright.design.tools.RelocationTools; import com.xilinx.rapidwright.device.IntentCode; @@ -125,6 +126,7 @@ private static void addFunction(String name, MainStyleFunction func) { static { addFunction("AddSubGenerator", AddSubGenerator::main); + addFunction("ArrayBuilder", ArrayBuilder::main); addFunction("BlockCreator", BlockCreator::main); addFunction("BlockStitcher", BlockStitcher::main); addFunction("BlockUpdater", BlockUpdater::main); From ebf372132df1a5151acbaeee3084b2598c5eab9d Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 6 Jun 2025 10:33:29 -0700 Subject: [PATCH 08/24] Copyright Signed-off-by: Eddie Hung --- src/com/xilinx/rapidwright/MainEntrypoint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/xilinx/rapidwright/MainEntrypoint.java b/src/com/xilinx/rapidwright/MainEntrypoint.java index 52ee89215..d9ffe2346 100644 --- a/src/com/xilinx/rapidwright/MainEntrypoint.java +++ b/src/com/xilinx/rapidwright/MainEntrypoint.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2021-2022, Xilinx, Inc. - * Copyright (c) 2022-2024, Advanced Micro Devices, Inc. + * Copyright (c) 2022-2025, Advanced Micro Devices, Inc. * All rights reserved. * * Author: Jakob Wenzel, Xilinx Research Labs. From 3d937a36f1788fc8fed51afece6a122f6ab17fcd Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 12 Jun 2025 16:59:21 -0600 Subject: [PATCH 09/24] Add option to provide a top-level design Signed-off-by: Chris Lavin --- .../design/tools/ArrayBuilder.java | 61 +++++++++++++++++-- src/com/xilinx/rapidwright/edif/EDIFCell.java | 22 +++++++ 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java index f9a902b29..568c4a6f5 100644 --- a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java @@ -31,8 +31,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Queue; import com.xilinx.rapidwright.design.Cell; import com.xilinx.rapidwright.design.ClockTools; @@ -59,6 +61,8 @@ import com.xilinx.rapidwright.edif.EDIFCell; import com.xilinx.rapidwright.edif.EDIFCellInst; import com.xilinx.rapidwright.edif.EDIFDirection; +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFHierNet; import com.xilinx.rapidwright.edif.EDIFNet; import com.xilinx.rapidwright.edif.EDIFNetlist; import com.xilinx.rapidwright.edif.EDIFPort; @@ -94,9 +98,12 @@ public class ArrayBuilder { private static final List REUSE_RESULTS_OPTS = Arrays.asList("r", "reuse"); private static final List SKIP_IMPL_OPTS = Arrays.asList("k", "skip-impl"); private static final List LIMIT_INSTS_OPTS = Arrays.asList("l", "limit-inst-count"); + private static final List TOP_LEVEL_DESIGN_OPTS = Arrays.asList("t", "top-design"); private Design design; + private Design topDesign; + private List pblocks; private double clkPeriod; @@ -129,6 +136,7 @@ private OptionParser createOptionParser() { acceptsAll(REUSE_RESULTS_OPTS, "Reuse Previous Implementation Results"); acceptsAll(SKIP_IMPL_OPTS, "Skip Implementation of the Kernel"); acceptsAll(LIMIT_INSTS_OPTS, "Limit number of instance copies").withRequiredArg(); + acceptsAll(TOP_LEVEL_DESIGN_OPTS, "Top level design with blackboxes/kernel insts").withRequiredArg(); acceptsAll(HELP_OPTS, "Print this help message").forHelp(); } }; @@ -166,6 +174,14 @@ public Design getDesign() { return design; } + public Design getTopDesign() { + return topDesign; + } + + public void setTopDesign(Design topDesign) { + this.topDesign = topDesign; + } + public void setPBlocks(List pblocks) { this.pblocks = pblocks; } @@ -309,6 +325,11 @@ private void initializeArrayBuilder(OptionSet options) { if (options.has(LIMIT_INSTS_OPTS.get(0))) { setInstCountLimit(Integer.parseInt((String) options.valueOf(LIMIT_INSTS_OPTS.get(0)))); } + + if (options.has(TOP_LEVEL_DESIGN_OPTS.get(0))) { + Design d = Design.readCheckpoint((String) options.valueOf(TOP_LEVEL_DESIGN_OPTS.get(0))); + setTopDesign(d); + } } @@ -345,6 +366,25 @@ public static void removeBUFGs(Design design) { } } + public static List getMatchingModuleInstanceNames(Module m, Design array) { + List instNames = new ArrayList<>(); + EDIFCell modCellType = m.getNetlist().getTopCell(); + EDIFHierCellInst top = array.getNetlist().getTopHierCellInst(); + Queue q = new LinkedList<>(); + q.add(top); + while (!q.isEmpty()) { + EDIFHierCellInst curr = q.poll(); + if (curr.getCellType().matchesInterface(modCellType)) { + instNames.add(curr.getFullHierarchicalInstName()); + } else { + for (EDIFCellInst child : curr.getCellType().getCellInsts()) { + q.add(curr.getChild(child)); + } + } + } + return instNames; + } + public static void main(String[] args) { CodePerfTracker t = new CodePerfTracker(ArrayBuilder.class.getName()); t.start("Init"); @@ -410,6 +450,7 @@ public static void main(String[] args) { // Just use the design we loaded and replicate it removeBUFGs(ab.getDesign()); Module m = new Module(ab.getDesign(), unrouteStaticNets); + m.getNet(ab.getClockName()).unroute(); m.calculateAllValidPlacements(ab.getDevice()); if (ab.getPBlocks().size() > 0) { m.setPBlock(ab.getPBlocks().get(0)); @@ -418,7 +459,16 @@ public static void main(String[] args) { } t.stop().start("Place Instances"); - Design array = new Design("array", ab.getDesign().getPartName()); + Design array = null; + List modInstNames = null; + if (ab.getTopDesign() == null) { + array = new Design("array", ab.getDesign().getPartName()); + } else { + array = ab.getTopDesign(); + // Find instances in existing design + modInstNames = getMatchingModuleInstanceNames(modules.get(0), array); + ab.setInstCountLimit(modInstNames.size()); + } ModuleInst curr = null; int placed = 0; @@ -426,7 +476,9 @@ public static void main(String[] args) { outer: for (Module module : modules) { for (Site anchor : module.getAllValidPlacements()) { if (curr == null) { - curr = array.createModuleInst("inst_" + i++, module); + String instName = modInstNames == null ? ("inst_" + i) : modInstNames.get(i); + curr = array.createModuleInst(instName, module); + i++; } if (curr.place(anchor, true, false)) { if (straddlesClockRegion(curr)) { @@ -449,9 +501,10 @@ public static void main(String[] args) { System.out.println("Found " + unrouted.size() + " overlapping nets, that were unrouted."); } - if (ab.isSkipImpl()) { + if (ab.isSkipImpl() && ab.getTopDesign() == null) { EDIFCell top = array.getTopEDIFCell(); - if (array.getNet(ab.getClockName()) == null) { + EDIFHierNet clkNet = array.getNetlist().getHierNetFromName(ab.getClockName()); + if (clkNet == null) { // Create BUFG and clock net, then connect to all instances Cell bufg = createBUFGCE(array, top, "bufg", array.getDevice().getSite("BUFGCE_X2Y0")); Net clk = array.createNet(ab.getClockName()); diff --git a/src/com/xilinx/rapidwright/edif/EDIFCell.java b/src/com/xilinx/rapidwright/edif/EDIFCell.java index f742aded8..d674b43ed 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFCell.java +++ b/src/com/xilinx/rapidwright/edif/EDIFCell.java @@ -735,5 +735,27 @@ public int getNonHierInstantiationCount() { public boolean isUniquified() { return getNonHierInstantiationCount() <= 1; } + + /** + * Checks if this cell and the provided cell have the same set of ports + * + * @param other The other cell to match against. + * @return True if the set of ports on both this cell and the other cell match + * exactly. False otherwise. + */ + public boolean matchesInterface(EDIFCell other) { + if (getPorts().size() != other.getPorts().size()) { + return false; + } + Map otherPorts = other.getPortMap(); + for (EDIFPort port : getPorts()) { + EDIFPort otherPort = otherPorts.get(port.getBusName(true)); + if (otherPort == null || port.getWidth() != otherPort.getWidth() + || port.getDirection() != otherPort.getDirection()) { + return false; + } + } + return true; + } } From bc23ffe8b1cebd8080fd89d703f04cd2071e191c Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 19 Jun 2025 17:19:07 -0600 Subject: [PATCH 10/24] Work-around for blackbox handling Signed-off-by: Chris Lavin --- .../rapidwright/design/tools/ArrayBuilder.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java index 568c4a6f5..30022f4ab 100644 --- a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java @@ -477,7 +477,24 @@ public static void main(String[] args) { for (Site anchor : module.getAllValidPlacements()) { if (curr == null) { String instName = modInstNames == null ? ("inst_" + i) : modInstNames.get(i); + // TODO - Remove after createModuleInst() fix + EDIFHierCellInst hierInst = array.getNetlist().getHierCellInstFromName(instName); + if (hierInst != null && hierInst.getCellType().isLeafCellOrBlackBox()) { + EDIFCell bb = hierInst.getCellType(); + if (bb.getLibrary() != null) { + bb.getLibrary().removeCell(bb); + } + EDIFCell modCell = module.getNetlist().getTopCell(); + EDIFCell currCell = array.getNetlist().getWorkLibrary().getCell(modCell.getName()); + if (currCell == null) { + array.getNetlist().copyCellAndSubCells(modCell); + } + } // END TODO curr = array.createModuleInst(instName, module); + // TODO - Remove after createModuleInst() fix + EDIFHierCellInst hierCellInst = array.getNetlist().getHierCellInstFromName(instName); + hierCellInst.getInst().removeBlackBoxProperty(); + // END TODO i++; } if (curr.place(anchor, true, false)) { From 1730b70abddbdca7714289db96279c3749109c80 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 19 Jun 2025 17:20:57 -0600 Subject: [PATCH 11/24] Update library when renaming cells; add API to remove bb prop Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/edif/EDIFCell.java | 6 ++++++ src/com/xilinx/rapidwright/edif/EDIFCellInst.java | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/com/xilinx/rapidwright/edif/EDIFCell.java b/src/com/xilinx/rapidwright/edif/EDIFCell.java index f986a2fa6..8b5109294 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFCell.java +++ b/src/com/xilinx/rapidwright/edif/EDIFCell.java @@ -341,7 +341,13 @@ public EDIFPort createPort(EDIFPort port) { } public void rename(String newName) { + if (getLibrary() != null) { + getLibrary().removeCell(this); + } setName(newName); + if (getLibrary() != null) { + getLibrary().addCell(this); + } } /** diff --git a/src/com/xilinx/rapidwright/edif/EDIFCellInst.java b/src/com/xilinx/rapidwright/edif/EDIFCellInst.java index 6925ee0ba..8e70c4c37 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFCellInst.java +++ b/src/com/xilinx/rapidwright/edif/EDIFCellInst.java @@ -274,6 +274,11 @@ public boolean isBlackBox() { return false; } + public void removeBlackBoxProperty() { + removeProperty(BLACK_BOX_PROP); + removeProperty(BLACK_BOX_PROP_VERSAL); + } + protected EDIFPortInstList getEDIFPortInstList() { return portInsts; } From d8c166cee7c4c6b9957876fa1cc3692df909625a Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Sat, 28 Jun 2025 21:52:28 -0600 Subject: [PATCH 12/24] Inline Flop Add/Removal Tool Signed-off-by: Chris Lavin --- .../design/tools/InlineFlopTools.java | 252 ++++++++++++++++++ .../rapidwright/eco/ECOPlacementHelper.java | 21 +- src/com/xilinx/rapidwright/edif/EDIFNet.java | 24 ++ .../xilinx/rapidwright/edif/EDIFNetlist.java | 19 +- src/com/xilinx/rapidwright/edif/EDIFPort.java | 26 ++ .../xilinx/rapidwright/util/StringTools.java | 19 ++ 6 files changed, 353 insertions(+), 8 deletions(-) create mode 100644 src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java diff --git a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java new file mode 100644 index 000000000..d1682f594 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java @@ -0,0 +1,252 @@ +/* + * 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.design.tools; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.xilinx.rapidwright.design.Cell; +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.DesignTools; +import com.xilinx.rapidwright.design.Net; +import com.xilinx.rapidwright.design.SiteInst; +import com.xilinx.rapidwright.design.SitePinInst; +import com.xilinx.rapidwright.design.Unisim; +import com.xilinx.rapidwright.design.blocks.PBlock; +import com.xilinx.rapidwright.device.BEL; +import com.xilinx.rapidwright.device.Site; +import com.xilinx.rapidwright.eco.ECOPlacementHelper; +import com.xilinx.rapidwright.edif.EDIFCell; +import com.xilinx.rapidwright.edif.EDIFCellInst; +import com.xilinx.rapidwright.edif.EDIFHierNet; +import com.xilinx.rapidwright.edif.EDIFNet; +import com.xilinx.rapidwright.edif.EDIFPort; +import com.xilinx.rapidwright.edif.EDIFPortInst; +import com.xilinx.rapidwright.util.Pair; +import com.xilinx.rapidwright.util.StringTools; + +/** + * Set of tools to add/remove flip flops inline to top level port connections. + * This is targeted at kernel replication preparation prior to routing to ensure + * that connections are routed outside of the pblock of the out-of-context + * kernel. + * + */ +public class InlineFlopTools { + + private static final String CLK_OPT = "--clk"; + private static final String PBLOCK_OPT = "--pblock"; + private static final String REMOVE_FLOPS_OPT = "--remove_flops"; + + private static final String INLINE_SUFFIX = "_rw_inline_flop"; + + /** + * Add flip flops inline on all the top-level ports of an out-of-context design. + * This is useful for placed out-of-context kernels prior to routing so that + * after the flops have been placed, the router is forced to route connections + * of each of the ports to each of the flops. This can help alleviate congestion + * when the kernels are placed/relocated in context. + * + * @param design The design to modify + * @param clkNet Name of the clock net to use on which to add the flops + * @param keepOut The pblock used to contain the kernel and the added flops will + * not be placed inside this area. + */ + public static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clkNet, PBlock keepOut) { + EDIFCell top = design.getTopEDIFCell(); + Site start = keepOut.getAllSites("SLICE").iterator().next(); // TODO this is a bit wasteful + boolean exclude = true; + Iterator siteItr = ECOPlacementHelper.spiralOutFrom(start, keepOut, exclude).iterator(); + siteItr.next(); // Skip the first site, as we are suggesting one inside the pblock + + Net clk = design.getNet(clkNet); + + Set siteInstsToRoute = new HashSet<>(); + + for (EDIFPort port : top.getPorts()) { + if (port.isBus()) { + for (int i : port.getBitBlastedIndicies()) { + EDIFPortInst inst = port.getInternalPortInstFromIndex(i); + Pair loc = nextAvailPlacement(design, siteItr); + Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } + } else { + EDIFPortInst inst = port.getInternalPortInst(); + Pair loc = nextAvailPlacement(design, siteItr); + Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } + } + for (SiteInst si : siteInstsToRoute) { + si.routeSite(); + } + } + + private static Pair nextAvailPlacement(Design design, Iterator itr) { + while (itr.hasNext()) { + Site curr = itr.next(); + SiteInst candidate = design.getSiteInstFromSite(curr); + if (candidate == null) { + // Empty site, let's use it + return new Pair(curr, curr.getBEL("AFF")); + } + } + return null; + } + + private static Cell createAndPlaceFlopInlineOnTopPortInst(Design design, EDIFPortInst portInst, Pair loc, + Net clk) { + String name = portInst.getFullName() + INLINE_SUFFIX; + Cell flop = design.createAndPlaceCell(design.getTopEDIFCell(), name, Unisim.FDRE, loc.getFirst(), + loc.getSecond()); + Net net = design.createNet(name); + net.connect(flop, portInst.isInput() ? "D" : "Q"); + design.getGndNet().connect(flop, "R"); + design.getVccNet().connect(flop, "CE"); + clk.connect(flop, "C"); + EDIFNet origNet = portInst.getNet(); + origNet.removePortInst(portInst); + net.getLogicalNet().addPortInst(portInst); + Net origPhysNet = design.getNet(origNet.getName()); + if (origPhysNet == null) { + if (origNet.isGND()) { + origPhysNet = design.getGndNet(); + } else if (origNet.isVCC()) { + origPhysNet = design.getVccNet(); + } else { + origPhysNet = design.createNet(new EDIFHierNet(design.getNetlist().getTopHierCellInst(), origNet)); + } + } + origPhysNet.connect(flop, portInst.isInput() ? "Q" : "D"); + return flop; + } + + /** + * Removes the inline flops added by + * {@link #createAndPlaceFlopsInlineOnTopPorts(Design, String, PBlock)} + * + * @param design The current design from which to remove the flops + */ + public static void removeInlineFlops(Design design) { + Map> pinsToRemove = new HashMap<>(); + List siteInstToRemove = new ArrayList<>(); + List cellsToRemove = new ArrayList<>(); + Net vcc = design.getVccNet(); + Set vccPins = new HashSet<>(); + pinsToRemove.put(vcc, vccPins); + for (EDIFCellInst inst : design.getTopEDIFCell().getCellInsts()) { + if (inst.getName().endsWith(INLINE_SUFFIX)) { + Cell flop = design.getCell(inst.getName()); + SiteInst si = flop.getSiteInst(); + // Assume we only placed one flop per SiteInst + siteInstToRemove.add(si); + for (SitePinInst pin : si.getSitePinInsts()) { + pinsToRemove.computeIfAbsent(pin.getNet(), p -> new HashSet<>()).add(pin); + } + vccPins.add(vcc.createPin("CKEN1", si)); + vccPins.add(vcc.createPin("RST", si)); + + cellsToRemove.add(inst); + } + } + + for (SiteInst si : siteInstToRemove) { + design.removeSiteInst(si); + } + DesignTools.batchRemoveSitePins(pinsToRemove, true); + + String[] ctrlPins = new String[] { "C", "R", "CE" }; + EDIFCell top = design.getTopEDIFCell(); + for (EDIFCellInst c : cellsToRemove) { + // Remove control set pins + for (String pin : ctrlPins) { + EDIFPortInst p = c.getPortInst(pin); + p.getNet().removePortInst(p); + } + // Merge 'D' sources and 'Q' sinks, restore original net + EDIFPortInst d = c.getPortInst("D"); + EDIFNet dNet = d.getNet(); + EDIFPortInst q = c.getPortInst("Q"); + EDIFNet qNet = q.getNet(); + if (dNet.getName().endsWith(INLINE_SUFFIX)) { + // Input port + for (EDIFPortInst pi : new ArrayList<>(d.getNet().getPortInsts())) { + if (pi.getCellInst() != c) { + dNet.removePortInst(pi); + qNet.addPortInst(pi); + } + } + qNet.removePortInst(q); + top.removeNet(dNet); + } else { + // Output port + for (EDIFPortInst pi : new ArrayList<>(q.getNet().getPortInsts())) { + if (pi.getCellInst() != c) { + qNet.removePortInst(pi); + dNet.addPortInst(pi); + } + } + top.removeNet(qNet); + dNet.removePortInst(d); + } + + top.removeCellInst(c); + } + + } + + public static void main(String[] args) { + if (args.length < 3 || args.length > 4) { + System.out.println("USAGE (to add flops) : "+CLK_OPT+"= "+PBLOCK_OPT+"="); + System.out.println("USAGE (to remove flops): " + REMOVE_FLOPS_OPT); + return; + } + + Design d = Design.readCheckpoint(args[0]); + + if (args[2].startsWith(CLK_OPT) || args[2].startsWith(PBLOCK_OPT)) { + d.unplaceDesign(); + String clkName = StringTools.getOptionValue(CLK_OPT, args); + String pblockRange = StringTools.getOptionValue(PBLOCK_OPT, args); + if (clkName == null || pblockRange == null) { + throw new RuntimeException("ERROR: Missing value(s) for option(s): " + + CLK_OPT + "=" + clkName + ", " + PBLOCK_OPT + "=" + pblockRange); + } + PBlock pblock = new PBlock(d.getDevice(), pblockRange); + createAndPlaceFlopsInlineOnTopPorts(d, clkName, pblock); + } else if (args[2].equals(REMOVE_FLOPS_OPT)) { + removeInlineFlops(d); + } else { + System.err.println("ERROR: Unrecognized option '" + args[2] +"'"); + } + + + d.writeCheckpoint(args[1]); + } +} diff --git a/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java b/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java index 7809605d6..c978637df 100644 --- a/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java +++ b/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java @@ -328,9 +328,28 @@ public static Iterable spiralOutFrom(Site site) { * @param site Originating Site. * @param pblock Also check to ensure the proposed sites are inside the provided * pblock. + * * @return Iterable of neighbouring sites. */ public static Iterable spiralOutFrom(Site site, PBlock pblock) { + return spiralOutFrom(site, pblock, false); + } + + /** + * Given a home Site, return an Iterable that yields the neighbouring sites + * encountered when walking outwards in a spiral fashion. To be used in + * conjunction with {@link #getUnusedLUT(SiteInst)} and + * {@link #getUnusedFlop(SiteInst, Net)}. + * + * @param site Originating Site. + * @param pblock Also check to ensure the proposed sites are inside the + * provided pblock. + * @param exclude If this flag is true, any sites inside the pblock are + * excluded. + * + * @return Iterable of neighbouring sites. + */ + public static Iterable spiralOutFrom(Site site, PBlock pblock, boolean exclude) { return new Iterable() { @NotNull @Override @@ -379,7 +398,7 @@ public Site next() { break; } nextSite = home.getNeighborSite(dx, dy); - } while (nextSite == null || !insidePblock(nextSite)); + } while (nextSite == null || (exclude ? insidePblock(nextSite) : !insidePblock(nextSite))); return retSite; } diff --git a/src/com/xilinx/rapidwright/edif/EDIFNet.java b/src/com/xilinx/rapidwright/edif/EDIFNet.java index 226a33a0f..fe4c6e108 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFNet.java +++ b/src/com/xilinx/rapidwright/edif/EDIFNet.java @@ -35,6 +35,7 @@ import java.util.List; import com.xilinx.rapidwright.design.Cell; +import com.xilinx.rapidwright.design.NetType; import com.xilinx.rapidwright.design.Unisim; /** @@ -408,6 +409,29 @@ private boolean checkIsStaticSource(String name, String cellType) { return true; } + /** + * Checks if this net is a GND or VCC net and if so returns the appropriate + * NetType. If it is not a static net, it returns NetType.UNKNOWN. + * + * @return The static source type or UNKNOWN if it is not a static net. + */ + public NetType getPhysStaticSourceType() { + if (getName().equals(EDIFTools.LOGICAL_GND_NET_NAME)) + return NetType.GND; + if (getName().equals(EDIFTools.LOGICAL_VCC_NET_NAME)) + return NetType.VCC; + for (EDIFPortInst portInst : getSourcePortInsts(false)) { + String cellType = portInst.getCellInst().getCellType().getName(); + if (cellType.equals(Unisim.GND.name())) { + return NetType.GND; + } + if (cellType.equals(Unisim.VCC.name())) { + return NetType.VCC; + } + } + return NetType.UNKNOWN; + } + /** * Checks if the net has all port instances (terminals) within the parent cell. * diff --git a/src/com/xilinx/rapidwright/edif/EDIFNetlist.java b/src/com/xilinx/rapidwright/edif/EDIFNetlist.java index c07721529..6b3b79d1b 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFNetlist.java +++ b/src/com/xilinx/rapidwright/edif/EDIFNetlist.java @@ -1146,10 +1146,10 @@ public Net getPhysicalNetFromPin(EDIFHierPortInst p, Design d) { } Map parentNetMap = getParentNetMap(); - EDIFHierNet parentNetName = parentNetMap.get(p.getHierarchicalNet()); - Net n = parentNetName == null ? null : d.getNet(parentNetName.getHierarchicalNetName()); + EDIFHierNet parentNet = parentNetMap.get(p.getHierarchicalNet()); + Net n = parentNet == null ? null : d.getNet(parentNet.getHierarchicalNetName()); if (n == null) { - if (parentNetName == null) { + if (parentNet == null) { // Maybe it is GND/VCC List src = p.getNet().getSourcePortInsts(false); if (src.size() > 0 && src.get(0).getCellInst() != null) { @@ -1158,12 +1158,12 @@ public Net getPhysicalNetFromPin(EDIFHierPortInst p, Design d) { if (cellType.equals("VCC")) return d.getVccNet(); } } - if (parentNetName == null) { + if (parentNet == null) { System.err.println("WARNING: Could not find parent of net \"" + p.getHierarchicalNet() + "\", please check that the netlist is fully connected through all levels of " + "hierarchy for this net."); } - EDIFNet logicalNet = parentNetName.getNet(); + EDIFNet logicalNet = parentNet.getNet(); List eprList = logicalNet.getSourcePortInsts(false); if (eprList.size() > 1) throw new RuntimeException("ERROR: Bad assumption on net, has two sources."); if (eprList.size() == 1) { @@ -1176,8 +1176,13 @@ public Net getPhysicalNetFromPin(EDIFHierPortInst p, Design d) { } // If size is 0, assume top level port in an OOC design - n = d.createNet(parentNetName.getHierarchicalNetName()); - n.setLogicalHierNet(parentNetName); + n = d.createNet(parentNet.getHierarchicalNetName()); + n.setLogicalHierNet(parentNet); + } else { + NetType staticType = p.getNet().getPhysStaticSourceType(); + if (staticType != NetType.UNKNOWN) { + d.getStaticNet(staticType); + } } return n; } diff --git a/src/com/xilinx/rapidwright/edif/EDIFPort.java b/src/com/xilinx/rapidwright/edif/EDIFPort.java index e64d576f4..ea30adf89 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFPort.java +++ b/src/com/xilinx/rapidwright/edif/EDIFPort.java @@ -233,6 +233,32 @@ public String getPortInstNameFromPort(int index) { return getBusName(true) + index + "]"; } + /** + * Gets the internal port instance connected to this port at the specified + * index. + * + * @param index Index of the bussed port instance to get. + * @return The EDIFPortInst connected to this port at the specified index, or + * null if none exists. + */ + public EDIFPortInst getInternalPortInstFromIndex(int index) { + String name = getPortInstNameFromPort(index); + EDIFNet net = getInternalNet(index); + return net.getPortInst(null, name); + } + + /** + * Gets the internal port instance connect to this port. Assumes this is a + * single bit port. + * + * @return The EDIFPortInst connected to this port, or null if none exists. + */ + public EDIFPortInst getInternalPortInst() { + assert (!isBus()); + EDIFNet net = getInternalNet(); + return net.getPortInst(null, getBusName()); + } + /** * Gets the internal port instance index from the named index for this port. * Given a bussed port 'bus[4:0]', the bus has an ordered list of named indices diff --git a/src/com/xilinx/rapidwright/util/StringTools.java b/src/com/xilinx/rapidwright/util/StringTools.java index 42924a50a..c52060bd0 100644 --- a/src/com/xilinx/rapidwright/util/StringTools.java +++ b/src/com/xilinx/rapidwright/util/StringTools.java @@ -301,6 +301,25 @@ public static void printListInColumns(List items, PrintStream ps, int ma } } + /** + * Light-weight helper method to get the value of an option in an array of + * arguments. For example, if option is '--option' and args is {"in.dcp", + * "out.dcp", "--option=value"}, this method will return 'value'. + * + * @param option The name of the option to search for + * @param args The list or arguments (usually from main()) + * @return The value of the option or null if it was not found + */ + public static String getOptionValue(String option, String[] args) { + for (String arg : args) { + if (arg.startsWith(option)) { + int idx = arg.indexOf('='); + return arg.substring(idx + 1).trim(); + } + } + return null; + } + public static void main(String[] args) { String[] tests = new String[] { "ARCHITECTURE", From 9dedc2ab09d46e955e2bc7f76341b44faea0ee59 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Mon, 30 Jun 2025 10:45:03 -0600 Subject: [PATCH 13/24] rc1 Signed-off-by: Chris Lavin --- .classpath | 4 ++-- .github/workflows/build.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.classpath b/.classpath index 88bc5f723..e0203f747 100644 --- a/.classpath +++ b/.classpath @@ -33,9 +33,9 @@ - + - + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d3d2b556c..68e60429a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: pull_request: env: - RAPIDWRIGHT_VERSION: v2025.1.0-beta + RAPIDWRIGHT_VERSION: v2025.1.1-rc1-beta jobs: build: From e15fe6d5368b6ac8b538e85d99c839204431cc57 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Mon, 30 Jun 2025 17:23:42 -0600 Subject: [PATCH 14/24] Adding a test Signed-off-by: Chris Lavin --- .../design/tools/InlineFlopTools.java | 21 ++++++++-- .../xilinx/rapidwright/util/VivadoTools.java | 39 +++++++++++++++++++ .../rapidwright/util/VivadoToolsHelper.java | 8 ++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java index d1682f594..77b25a925 100644 --- a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java +++ b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java @@ -39,6 +39,7 @@ import com.xilinx.rapidwright.design.Unisim; import com.xilinx.rapidwright.design.blocks.PBlock; import com.xilinx.rapidwright.device.BEL; +import com.xilinx.rapidwright.device.Series; import com.xilinx.rapidwright.device.Site; import com.xilinx.rapidwright.eco.ECOPlacementHelper; import com.xilinx.rapidwright.edif.EDIFCell; @@ -63,14 +64,16 @@ public class InlineFlopTools { private static final String PBLOCK_OPT = "--pblock"; private static final String REMOVE_FLOPS_OPT = "--remove_flops"; - private static final String INLINE_SUFFIX = "_rw_inline_flop"; + public static final String INLINE_SUFFIX = "_rw_inline_flop"; /** * Add flip flops inline on all the top-level ports of an out-of-context design. * This is useful for placed out-of-context kernels prior to routing so that * after the flops have been placed, the router is forced to route connections * of each of the ports to each of the flops. This can help alleviate congestion - * when the kernels are placed/relocated in context. + * when the kernels are placed/relocated in context. Note this assumes the + * design is not implemented as in most contexts it will be placed and routed + * immediately following this modification. * * @param design The design to modify * @param clkNet Name of the clock net to use on which to add the flops @@ -78,6 +81,7 @@ public class InlineFlopTools { * not be placed inside this area. */ public static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clkNet, PBlock keepOut) { + assert (design.getSiteInsts().size() == 0); EDIFCell top = design.getTopEDIFCell(); Site start = keepOut.getAllSites("SLICE").iterator().next(); // TODO this is a bit wasteful boolean exclude = true; @@ -89,6 +93,10 @@ public static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clk Set siteInstsToRoute = new HashSet<>(); for (EDIFPort port : top.getPorts()) { + // Don't flop the clock net + if (port.getName().equals(clkNet)) { + continue; + } if (port.isBus()) { for (int i : port.getBitBlastedIndicies()) { EDIFPortInst inst = port.getInternalPortInstFromIndex(i); @@ -160,6 +168,7 @@ public static void removeInlineFlops(Design design) { Net vcc = design.getVccNet(); Set vccPins = new HashSet<>(); pinsToRemove.put(vcc, vccPins); + String[] staticPins = new String[] { "CKEN1", design.getSeries() == Series.Versal ? "RST" : "SRST1" }; for (EDIFCellInst inst : design.getTopEDIFCell().getCellInsts()) { if (inst.getName().endsWith(INLINE_SUFFIX)) { Cell flop = design.getCell(inst.getName()); @@ -169,8 +178,11 @@ public static void removeInlineFlops(Design design) { for (SitePinInst pin : si.getSitePinInsts()) { pinsToRemove.computeIfAbsent(pin.getNet(), p -> new HashSet<>()).add(pin); } - vccPins.add(vcc.createPin("CKEN1", si)); - vccPins.add(vcc.createPin("RST", si)); + for (String staticPin : staticPins) { + if (si.getSitePinInst(staticPin) == null) { + vccPins.add(vcc.createPin(staticPin, si)); + } + } cellsToRemove.add(inst); } @@ -217,6 +229,7 @@ public static void removeInlineFlops(Design design) { } top.removeCellInst(c); + design.removeCell(c.getName()); } } diff --git a/src/com/xilinx/rapidwright/util/VivadoTools.java b/src/com/xilinx/rapidwright/util/VivadoTools.java index a257c171f..92da0234d 100644 --- a/src/com/xilinx/rapidwright/util/VivadoTools.java +++ b/src/com/xilinx/rapidwright/util/VivadoTools.java @@ -388,6 +388,45 @@ public static ReportRouteStatusResult routeDesignAndGetStatus(Design design, Pat return routeDesignAndGetStatus(dcp, workdir, encrypted); } + /** + * Run Vivado's `place_design` and `route_design` command on the design provided + * and get the `report_route_status` results. Note: this method does not + * preserve the routed output from Vivado. + * + * @param design The design to route and report on. + * @param workdir Directory to work within. + * @return The results of `report_route_status`. + */ + public static ReportRouteStatusResult placeAndRouteDesignAndGetStatus(Design design, Path workdir) { + boolean encrypted = !design.getNetlist().getEncryptedCells().isEmpty(); + Path dcp = workdir.resolve("placeAndRouteDesignAndGetStatus.dcp"); + design.writeCheckpoint(dcp); + return placeAndRouteDesignAndGetStatus(dcp, workdir, encrypted); + } + + /** + * Run Vivado's `place_design` and `route_design` command on the provided DCP + * path and return the `report_route_status` results. Note: this method does not + * preserve the routed output from Vivado. + * + * @param dcp Path to DCP to route and report on. + * @param workdir Directory to work within. + * @param encrypted Indicates whether DCP contains encrypted EDIF cells. + * @return The results of `report_route_status`. + */ + public static ReportRouteStatusResult placeAndRouteDesignAndGetStatus(Path dcp, Path workdir, boolean encrypted) { + final Path outputLog = workdir.resolve("outputLog.log"); + + StringBuilder sb = new StringBuilder(); + sb.append(createTclDCPLoadCommand(dcp, encrypted)); + sb.append(PLACE_DESIGN + "; "); + sb.append(ROUTE_DESIGN + "; "); + sb.append(REPORT_ROUTE_STATUS + "; "); + + List log = VivadoTools.runTcl(outputLog, sb.toString(), true); + return new ReportRouteStatusResult(log); + } + /** * Run Vivado's `route_design` command on the provided DCP path and return the * `report_route_status` results. Note: this method does not preserve the routed diff --git a/test/shared/com/xilinx/rapidwright/util/VivadoToolsHelper.java b/test/shared/com/xilinx/rapidwright/util/VivadoToolsHelper.java index b106dafa4..c21ff4f4f 100644 --- a/test/shared/com/xilinx/rapidwright/util/VivadoToolsHelper.java +++ b/test/shared/com/xilinx/rapidwright/util/VivadoToolsHelper.java @@ -60,4 +60,12 @@ public static void assertRoutedSuccessfullyByVivado(Design design, Path dir) { ReportRouteStatusResult rrs = VivadoTools.routeDesignAndGetStatus(design, dir); Assertions.assertTrue(rrs.isFullyRouted()); } + + public static void assertCanBeFullyPlacedAndRoutedByVivado(Design design, Path dir) { + if (!FileTools.isVivadoOnPath()) { + return; + } + ReportRouteStatusResult rrs = VivadoTools.placeAndRouteDesignAndGetStatus(design, dir); + Assertions.assertTrue(rrs.isFullyRouted()); + } } From 521578f7d46b181c14532338ec946d75c89340ac Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Fri, 19 Sep 2025 17:35:51 -0600 Subject: [PATCH 15/24] Merge encrypted cells from modules Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java index 30022f4ab..5162aaba4 100644 --- a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java @@ -489,6 +489,11 @@ public static void main(String[] args) { if (currCell == null) { array.getNetlist().copyCellAndSubCells(modCell); } + // Merge encrypted cells + List encryptedCells = module.getNetlist().getEncryptedCells(); + if (encryptedCells.size() > 0) { + array.getNetlist().addEncryptedCells(encryptedCells); + } } // END TODO curr = array.createModuleInst(instName, module); // TODO - Remove after createModuleInst() fix From 5c6414144e8076916e37386b1cb5f2468f16e1a7 Mon Sep 17 00:00:00 2001 From: Andrew Butt Date: Wed, 1 Oct 2025 12:01:17 -0600 Subject: [PATCH 16/24] Add features to PerformanceExplorer, ArrayBuilder, and InlineFlopTools (#1291) * Add ability for PerformanceExplorer to ensure external routability with InlineFlopTools Signed-off-by: Andrew Butt * Add ability to place flip-flops around array in array_builder to make out_of_context designs more realistic Signed-off-by: Andrew Butt * Add automatic PBlock selection and allow 5 inline flip-flops to be placed in a slice Signed-off-by: Andrew Butt * Add option to specify whether ArrayBuilder design in OOC Signed-off-by: Andrew Butt * Refactor getNetsWithOverlappingNodes Signed-off-by: Andrew Butt * Fix java 8 compat Signed-off-by: Andrew Butt * Address comments Signed-off-by: Andrew Butt * Swap order of SLICE and DSP checks Co-authored-by: Chris Lavin Signed-off-by: Andrew Butt * Prevent flops from being added to the clock on fully ooc designs (no ibuf on clock) Signed-off-by: Andrew Butt --------- Signed-off-by: Andrew Butt Signed-off-by: Andrew Butt Co-authored-by: Andrew Butt Co-authored-by: Chris Lavin --- .../xilinx/rapidwright/design/NetTools.java | 21 +- .../design/tools/ArrayBuilder.java | 378 ++++++++++++++---- .../design/tools/InlineFlopTools.java | 193 ++++++--- .../rapidwright/eco/ECOPlacementHelper.java | 4 +- src/com/xilinx/rapidwright/edif/EDIFPort.java | 2 +- .../rapidwright/util/PerformanceExplorer.java | 102 ++++- 6 files changed, 553 insertions(+), 147 deletions(-) diff --git a/src/com/xilinx/rapidwright/design/NetTools.java b/src/com/xilinx/rapidwright/design/NetTools.java index efe21861f..a479fea63 100644 --- a/src/com/xilinx/rapidwright/design/NetTools.java +++ b/src/com/xilinx/rapidwright/design/NetTools.java @@ -197,7 +197,21 @@ public static boolean hasClockSinks(Net net) { * @return The list of nets that were unrouted. */ public static List unrouteNetsWithOverlappingNodes(Design design) { - List unroutedNets = new ArrayList<>(); + List overlappingNets = getNetsWithOverlappingNodes(design); + for (Net net : overlappingNets) { + net.unroute(); + } + return overlappingNets; + } + + /** + * Returns a list of nets with overlapping nodes without unrouting them. + * + * @param design The design to evaluate for conflicting nodes. + * @return The list of nets that overlap. + */ + public static List getNetsWithOverlappingNodes(Design design) { + List overlappingNets = new ArrayList<>(); Map used = new HashMap<>(); for (Net net : design.getNets()) { for (PIP pip : net.getPIPs()) { @@ -211,12 +225,11 @@ public static List unrouteNetsWithOverlappingNodes(Design design) { used.remove(oldNode); } } - existing.unroute(); - unroutedNets.add(existing); + overlappingNets.add(existing); } } } } - return unroutedNets; + return overlappingNets; } } diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java index 5162aaba4..4b03a42c5 100644 --- a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java @@ -30,11 +30,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; import com.xilinx.rapidwright.design.Cell; import com.xilinx.rapidwright.design.ClockTools; @@ -57,6 +62,7 @@ import com.xilinx.rapidwright.device.PartNameTools; import com.xilinx.rapidwright.device.Series; import com.xilinx.rapidwright.device.Site; +import com.xilinx.rapidwright.device.SiteTypeEnum; import com.xilinx.rapidwright.device.Tile; import com.xilinx.rapidwright.edif.EDIFCell; import com.xilinx.rapidwright.edif.EDIFCellInst; @@ -79,13 +85,17 @@ import joptsimple.OptionParser; import joptsimple.OptionSet; +import static com.xilinx.rapidwright.util.Utils.isBRAM; +import static com.xilinx.rapidwright.util.Utils.isDSP; +import static com.xilinx.rapidwright.util.Utils.isSLICE; + /** * A Tool to optimize, place and route a kernel and then replicate its * implementation in an array across the fabric. */ public class ArrayBuilder { - private static final List INPUT_DESIGN_OPTS = Arrays.asList("i", "input"); + private static final List KERNEL_DESIGN_OPTS = Arrays.asList("i", "input"); private static final List OUTPUT_DESIGN_OPTS = Arrays.asList("o", "output"); private static final List INPUT_EDIF_OPTS = Arrays.asList("e", "edif"); private static final List UTILIZATION_OPTS = Arrays.asList("u", "utilization"); @@ -94,11 +104,16 @@ public class ArrayBuilder { private static final List PBLOCK_OPTS = Arrays.asList("b", "pblock"); private static final List HELP_OPTS = Arrays.asList("?", "h", "help"); private static final List TARGET_CLK_PERIOD_OPTS = Arrays.asList("c", "clk-period"); - private static final List TARGET_CLK_NAME_OPTS = Arrays.asList("n", "clk-name"); + private static final List KERNEL_CLK_NAME_OPTS = Arrays.asList("n", "kernel-clk-name"); + private static final List TOP_CLK_NAME_OPTS = Arrays.asList("m", "top-clk-name"); private static final List REUSE_RESULTS_OPTS = Arrays.asList("r", "reuse"); private static final List SKIP_IMPL_OPTS = Arrays.asList("k", "skip-impl"); private static final List LIMIT_INSTS_OPTS = Arrays.asList("l", "limit-inst-count"); private static final List TOP_LEVEL_DESIGN_OPTS = Arrays.asList("t", "top-design"); + private static final List WRITE_PLACEMENT_OPTS = Collections.singletonList("write-placement"); + private static final List PLACEMENT_FILE_OPTS = Collections.singletonList("read-placement"); + private static final List PLACEMENT_GRID_OPTS = Collections.singletonList("write-placement-grid"); + private static final List OUT_OF_CONTEXT_OPTS = Collections.singletonList("out-of-context"); private Design design; @@ -108,7 +123,9 @@ public class ArrayBuilder { private double clkPeriod; - private String clkName; + private String kernelClkName; + + private String topClkName; private boolean skipImpl; @@ -118,13 +135,21 @@ public class ArrayBuilder { private int instCountLimit = Integer.MAX_VALUE; + private String outputPlacementFileName; + + private String inputPlacementFileName; + + private String outputPlacementLocsFileName; + + private boolean outOfContext; + public static final double DEFAULT_CLK_PERIOD_TARGET = 2.0; private OptionParser createOptionParser() { OptionParser p = new OptionParser() { { - acceptsAll(INPUT_DESIGN_OPTS, "Input Kernel Design (*.dcp or *.edf)").withRequiredArg(); + acceptsAll(KERNEL_DESIGN_OPTS, "Input Kernel Design (*.dcp or *.edf)").withRequiredArg(); acceptsAll(OUTPUT_DESIGN_OPTS, "Output Array Design (default is 'array.dcp')").withRequiredArg(); acceptsAll(PBLOCK_OPTS, "PBlock Constraint(s), separated with ';'").withRequiredArg(); acceptsAll(INPUT_EDIF_OPTS, "Companion EDIF for DCP (*.edf)").withRequiredArg(); @@ -132,11 +157,16 @@ private OptionParser createOptionParser() { acceptsAll(SHAPES_OPTS, "Vivado Generated Shapes").withRequiredArg(); acceptsAll(PART_OPTS, "Target AMD Part").withRequiredArg(); acceptsAll(TARGET_CLK_PERIOD_OPTS, "Target Clock Period (ns)").withRequiredArg(); - acceptsAll(TARGET_CLK_NAME_OPTS, "Target Clock Name").withRequiredArg(); + acceptsAll(KERNEL_CLK_NAME_OPTS, "Kernel Clock Name").withRequiredArg(); + acceptsAll(TOP_CLK_NAME_OPTS, "Top Clock Name").withRequiredArg(); acceptsAll(REUSE_RESULTS_OPTS, "Reuse Previous Implementation Results"); acceptsAll(SKIP_IMPL_OPTS, "Skip Implementation of the Kernel"); acceptsAll(LIMIT_INSTS_OPTS, "Limit number of instance copies").withRequiredArg(); + acceptsAll(WRITE_PLACEMENT_OPTS, "Write the chosen placement to the specified file").withRequiredArg(); + acceptsAll(PLACEMENT_FILE_OPTS, "Use placement specified in file").withRequiredArg(); + acceptsAll(PLACEMENT_GRID_OPTS, "Write grid of possible placement locations to specified file").withRequiredArg(); acceptsAll(TOP_LEVEL_DESIGN_OPTS, "Top level design with blackboxes/kernel insts").withRequiredArg(); + acceptsAll(OUT_OF_CONTEXT_OPTS, "Specifies that the array will be compiled out of context"); acceptsAll(HELP_OPTS, "Print this help message").forHelp(); } }; @@ -163,14 +193,14 @@ private static void printHelp(OptionParser p) { } public Device getDevice() { - return getDesign().getDevice(); + return getKernelDesign().getDevice(); } - public void setDesign(Design design) { + public void setKernelDesign(Design design) { this.design = design; } - public Design getDesign() { + public Design getKernelDesign() { return design; } @@ -198,12 +228,20 @@ public double getClockPeriod() { return clkPeriod; } - public void setClockName(String clkName) { - this.clkName = clkName; + public void setKernelClockName(String clkName) { + this.kernelClkName = clkName; } - public String getClockName() { - return clkName; + public String getKernelClockName() { + return kernelClkName; + } + + public void setTopClockName(String clkName) { + this.topClkName = clkName; + } + + public String getTopClockName() { + return topClkName; } public boolean isSkipImpl() { @@ -230,21 +268,57 @@ public void setInstCountLimit(int instCountLimit) { this.instCountLimit = instCountLimit; } + public String getOutputPlacementFileName() { + return outputPlacementFileName; + } + + public void setOutputPlacementFileName(String outputPlacementFileName) { + this.outputPlacementFileName = outputPlacementFileName; + } + + public String getInputPlacementFileName() { + return inputPlacementFileName; + } + + public void setInputPlacementFileName(String inputPlacementFileName) { + this.inputPlacementFileName = inputPlacementFileName; + } + + public String getOutputPlacementLocsFileName() { + return outputPlacementLocsFileName; + } + + public void setOutputPlacementLocsFileName(String outputPlacementLocsFileName) { + this.outputPlacementLocsFileName = outputPlacementLocsFileName; + } + + public boolean getOutOfContext() { + return outOfContext; + } + + public void setOutOfContext(boolean outOfContext) { + this.outOfContext = outOfContext; + } + private void initializeArrayBuilder(OptionSet options) { Path inputFile = null; - if (options.has(SKIP_IMPL_OPTS.get(0))) { - this.skipImpl = true; - } + setSkipImpl(options.has(SKIP_IMPL_OPTS.get(0))); + setOutOfContext(options.has(OUT_OF_CONTEXT_OPTS.get(0))); - if (options.has(INPUT_DESIGN_OPTS.get(0))) { - inputFile = Paths.get((String)options.valueOf(INPUT_DESIGN_OPTS.get(0))); + if (options.has(KERNEL_DESIGN_OPTS.get(0))) { + inputFile = Paths.get((String) options.valueOf(KERNEL_DESIGN_OPTS.get(0))); if (inputFile.toString().endsWith(".dcp")) { if (options.has(INPUT_EDIF_OPTS.get(0))) { - Path companionEDIF = Paths.get((String)options.valueOf(INPUT_EDIF_OPTS.get(0))); - setDesign(Design.readCheckpoint(inputFile, companionEDIF, CodePerfTracker.SILENT)); + Path companionEDIF = Paths.get((String) options.valueOf(INPUT_EDIF_OPTS.get(0))); + setKernelDesign(Design.readCheckpoint(inputFile, companionEDIF, CodePerfTracker.SILENT)); } else { - setDesign(Design.readCheckpoint(inputFile, CodePerfTracker.SILENT)); + setKernelDesign(Design.readCheckpoint(inputFile)); + if (!design.getNetlist().getEncryptedCells().isEmpty()) { + System.out.println("Design has encrypted cells"); + } else { + System.out.println("Design does not have encrypted cells"); + } } } else if (inputFile.toString().endsWith(".edf")) { @@ -253,14 +327,14 @@ private void initializeArrayBuilder(OptionSet options) { Part part = PartNameTools.getPart((String) options.valueOf(PART_OPTS.get(0))); EDIFTools.ensureCorrectPartInEDIF(netlist, part.toString()); } - setDesign(new Design(netlist)); + setKernelDesign(new Design(netlist)); } } else { throw new RuntimeException("No input design found. " + "Please specify an input kernel (*.dcp or *.edf) using options " - + INPUT_DESIGN_OPTS); + + KERNEL_DESIGN_OPTS); } - assert (getDesign() != null); + assert (getKernelDesign() != null); if (options.has(PBLOCK_OPTS.get(0))) { String pblockString = (String) options.valueOf(PBLOCK_OPTS.get(0)); @@ -310,10 +384,10 @@ private void initializeArrayBuilder(OptionSet options) { System.out.println("[INFO] No clock period set, defaulting to: " + getClockPeriod() + "ns"); } - if (options.has(TARGET_CLK_NAME_OPTS.get(0))) { - setClockName(((String) options.valueOf(TARGET_CLK_NAME_OPTS.get(0)))); + if (options.has(KERNEL_CLK_NAME_OPTS.get(0))) { + setKernelClockName(((String) options.valueOf(KERNEL_CLK_NAME_OPTS.get(0)))); } else { - setClockName(ClockTools.getClockFromDesign(getDesign()).toString()); + setKernelClockName(ClockTools.getClockFromDesign(getKernelDesign()).toString()); } if (options.has(OUTPUT_DESIGN_OPTS.get(0))) { @@ -321,16 +395,33 @@ private void initializeArrayBuilder(OptionSet options) { } else { setOutputName("array.dcp"); } - + if (options.has(LIMIT_INSTS_OPTS.get(0))) { setInstCountLimit(Integer.parseInt((String) options.valueOf(LIMIT_INSTS_OPTS.get(0)))); } - + if (options.has(TOP_LEVEL_DESIGN_OPTS.get(0))) { Design d = Design.readCheckpoint((String) options.valueOf(TOP_LEVEL_DESIGN_OPTS.get(0))); setTopDesign(d); } + if (options.has(TOP_CLK_NAME_OPTS.get(0))) { + setTopClockName(((String) options.valueOf(TOP_CLK_NAME_OPTS.get(0)))); + } else { + setTopClockName(ClockTools.getClockFromDesign(getTopDesign()).toString()); + } + + if (options.has(WRITE_PLACEMENT_OPTS.get(0))) { + setOutputPlacementFileName((String) options.valueOf(WRITE_PLACEMENT_OPTS.get(0))); + } + + if (options.has(PLACEMENT_FILE_OPTS.get(0))) { + setInputPlacementFileName((String) options.valueOf(PLACEMENT_FILE_OPTS.get(0))); + } + + if (options.has(PLACEMENT_GRID_OPTS.get(0))) { + setOutputPlacementLocsFileName((String) options.valueOf(PLACEMENT_GRID_OPTS.get(0))); + } } public static void removeBUFGs(Design design) { @@ -385,6 +476,67 @@ public static List getMatchingModuleInstanceNames(Module m, Design array return instNames; } + public static void writePlacementToFile(Map placementMap, String fileName) { + List lines = new ArrayList<>(); + Comparator comparator = new Comparator() { + @Override + public int compare(Site o1, Site o2) { + if (o1.getInstanceY() > o2.getInstanceY()) { + return -1; + } + + if (o1.getInstanceY() < o2.getInstanceY()) { + return 1; + } + + return Integer.compare(o1.getInstanceX(), o2.getInstanceX()); + } + }; + Map sortedMap = placementMap.entrySet().stream() + .sorted(Map.Entry.comparingByValue(comparator)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, + (e1, e2) -> e1, LinkedHashMap::new)); + for (Map.Entry entry : sortedMap.entrySet()) { + lines.add(entry.getKey() + " " + entry.getValue()); + } + FileTools.writeLinesToTextFile(lines, fileName); + } + + public static Map readPlacementFromFile(String fileName) { + Map placementMap = new HashMap<>(); + List lines = FileTools.getLinesFromTextFile(fileName); + + for (String line : lines) { + String[] splitLine = line.split("\\s+"); + placementMap.put(splitLine[0], splitLine[1]); + } + + return placementMap; + } + + public static void writePlacementLocsToFile(List modules, String fileName) { + List lines = new ArrayList<>(); + Comparator comparator = (o1, o2) -> { + if (o1.getInstanceY() > o2.getInstanceY()) { + return -1; + } + + if (o1.getInstanceY() < o2.getInstanceY()) { + return 1; + } + + return Integer.compare(o1.getInstanceX(), o2.getInstanceX()); + }; + for (Module module : modules) { + lines.add(module.getName() + ":"); + List validPlacements = module.getAllValidPlacements().stream().sorted(comparator).collect(Collectors.toList()); + for (Site anchor : validPlacements) { + lines.add(anchor.getName()); + } + } + FileTools.writeLinesToTextFile(lines, fileName); + } + public static void main(String[] args) { CodePerfTracker t = new CodePerfTracker(ArrayBuilder.class.getName()); t.start("Init"); @@ -407,17 +559,17 @@ public static void main(String[] args) { workDir = Paths.get((String) options.valueOf(REUSE_RESULTS_OPTS.get(0))); reuseResults = true; } - t.stop().start("Implement Kernel"); List modules = new ArrayList<>(); boolean unrouteStaticNets = false; if (!ab.isSkipImpl()) { + t.stop().start("Implement Kernel"); FileTools.makeDirs(workDir.toString()); System.out.println("[INFO] Created work directory: " + workDir.toString()); // Initialize PerformanceExplorer - PerformanceExplorer pe = new PerformanceExplorer(ab.getDesign(), workDir.toString(), - ab.getClockName(), ab.getClockPeriod()); + PerformanceExplorer pe = new PerformanceExplorer(ab.getKernelDesign(), workDir.toString(), + ab.getKernelClockName(), ab.getClockPeriod()); // Set PBlocks Map pblocks = new HashMap<>(); @@ -448,21 +600,24 @@ public static void main(String[] args) { } } else /* skipImpl==true */ { // Just use the design we loaded and replicate it - removeBUFGs(ab.getDesign()); - Module m = new Module(ab.getDesign(), unrouteStaticNets); - m.getNet(ab.getClockName()).unroute(); - m.calculateAllValidPlacements(ab.getDevice()); - if (ab.getPBlocks().size() > 0) { + t.stop().start("Calculate Valid Placements"); + removeBUFGs(ab.getKernelDesign()); + Module m = new Module(ab.getKernelDesign(), unrouteStaticNets); + m.getNet(ab.getKernelClockName()).unroute(); + if (ab.getInputPlacementFileName() == null) { + m.calculateAllValidPlacements(ab.getDevice()); + } + if (!ab.getPBlocks().isEmpty()) { m.setPBlock(ab.getPBlocks().get(0)); } modules.add(m); } t.stop().start("Place Instances"); - Design array = null; + Design array = null; List modInstNames = null; if (ab.getTopDesign() == null) { - array = new Design("array", ab.getDesign().getPartName()); + array = new Design("array", ab.getKernelDesign().getPartName()); } else { array = ab.getTopDesign(); // Find instances in existing design @@ -470,74 +625,112 @@ public static void main(String[] args) { ab.setInstCountLimit(modInstNames.size()); } - ModuleInst curr = null; + if (ab.getOutputPlacementLocsFileName() != null) { + writePlacementLocsToFile(modules, ab.getOutputPlacementLocsFileName()); + } + + // Add encrypted cells from modules to array + for (Module module : modules) { + // Merge encrypted cells + List encryptedCells = module.getNetlist().getEncryptedCells(); + if (!encryptedCells.isEmpty()) { + System.out.println("Encrypted cells merged"); + array.getNetlist().addEncryptedCells(encryptedCells); + } + } + int placed = 0; - int i = 0; - outer: for (Module module : modules) { - for (Site anchor : module.getAllValidPlacements()) { - if (curr == null) { - String instName = modInstNames == null ? ("inst_" + i) : modInstNames.get(i); - // TODO - Remove after createModuleInst() fix - EDIFHierCellInst hierInst = array.getNetlist().getHierCellInstFromName(instName); - if (hierInst != null && hierInst.getCellType().isLeafCellOrBlackBox()) { - EDIFCell bb = hierInst.getCellType(); - if (bb.getLibrary() != null) { - bb.getLibrary().removeCell(bb); + Map newPlacementMap = new HashMap<>(); + if (ab.getInputPlacementFileName() != null) { + Map placementMap = readPlacementFromFile(ab.inputPlacementFileName); + System.out.println("Placing from specified file"); + for (Map.Entry entry : placementMap.entrySet()) { + String instName = entry.getKey(); + String anchorName = entry.getValue(); + EDIFHierCellInst hierInst = array.getNetlist().getHierCellInstFromName(instName); + if (hierInst == null) { + throw new RuntimeException("Instance name " + instName + " is invalid"); + } + if (modules.size() > 1) { + throw new RuntimeException("Manual placement does not work with automated implementation"); + } + Module module = modules.get(0); + ModuleInst curr = array.createModuleInst(instName, module); + + Site anchor = array.getDevice().getSite(anchorName); + + boolean wasPlaced = curr.place(anchor, true, false); + if (!wasPlaced) { + throw new RuntimeException("Unable to place cell " + instName + " at site " + anchor); + } + + if (straddlesClockRegion(curr)) { + curr.unplace(); + throw new RuntimeException("Chosen site anchor " + anchor + " straddles multiple clock regions"); + } + + newPlacementMap.put(curr, anchor); + placed++; + System.out.println(" ** PLACED: " + placed + " " + anchor + " " + curr.getName()); + } + } else { + ModuleInst curr = null; + int i = 0; + outer: for (Module module : modules) { + for (Site anchor : module.getAllValidPlacements()) { + if (curr == null) { + String instName = modInstNames == null ? ("inst_" + i) : modInstNames.get(i); + curr = array.createModuleInst(instName, module); + i++; + } + if (curr.place(anchor, true, false)) { + if (straddlesClockRegion(curr)) { + curr.unplace(); + continue; } - EDIFCell modCell = module.getNetlist().getTopCell(); - EDIFCell currCell = array.getNetlist().getWorkLibrary().getCell(modCell.getName()); - if (currCell == null) { - array.getNetlist().copyCellAndSubCells(modCell); + + List overlapping = NetTools.getNetsWithOverlappingNodes(array); + if (!overlapping.isEmpty()) { + curr.unplace(); + continue; } - // Merge encrypted cells - List encryptedCells = module.getNetlist().getEncryptedCells(); - if (encryptedCells.size() > 0) { - array.getNetlist().addEncryptedCells(encryptedCells); + + placed++; + newPlacementMap.put(curr, anchor); + System.out.println(" ** PLACED: " + placed + " " + anchor + " " + curr.getName()); + curr = null; + if (placed >= ab.getInstCountLimit()) { + break outer; } - } // END TODO - curr = array.createModuleInst(instName, module); - // TODO - Remove after createModuleInst() fix - EDIFHierCellInst hierCellInst = array.getNetlist().getHierCellInstFromName(instName); - hierCellInst.getInst().removeBlackBoxProperty(); - // END TODO - i++; - } - if (curr.place(anchor, true, false)) { - if (straddlesClockRegion(curr)) { - curr.unplace(); - continue; - } - placed++; - System.out.println(" ** PLACED: " + placed + " " + anchor + " " + curr.getName()); - curr = null; - if (placed >= ab.getInstCountLimit()) { - break outer; } } } } + if (ab.getOutputPlacementFileName() != null) { + writePlacementToFile(newPlacementMap, ab.outputPlacementFileName); + } List unrouted = NetTools.unrouteNetsWithOverlappingNodes(array); - if (unrouted.size() > 0) { + if (!unrouted.isEmpty()) { System.out.println("Found " + unrouted.size() + " overlapping nets, that were unrouted."); } if (ab.isSkipImpl() && ab.getTopDesign() == null) { EDIFCell top = array.getTopEDIFCell(); - EDIFHierNet clkNet = array.getNetlist().getHierNetFromName(ab.getClockName()); + EDIFHierNet clkNet = array.getNetlist().getHierNetFromName(ab.getKernelClockName()); if (clkNet == null) { // Create BUFG and clock net, then connect to all instances Cell bufg = createBUFGCE(array, top, "bufg", array.getDevice().getSite("BUFGCE_X2Y0")); - Net clk = array.createNet(ab.getClockName()); + Net clk = array.createNet(ab.getKernelClockName()); clk.connect(bufg, "O"); - Net clkIn = array.createNet(ab.getClockName() + "_in"); + Net clkIn = array.createNet(ab.getKernelClockName() + "_in"); clkIn.connect(bufg, "I"); - EDIFPort clkInPort = top.createPort(ab.getClockName(), EDIFDirection.INPUT, 1); + EDIFPort clkInPort = top.createPort(ab.getKernelClockName(), EDIFDirection.INPUT, 1); clkIn.getLogicalNet().createPortInst(clkInPort); EDIFNet logClkNet = clk.getLogicalNet(); for (EDIFCellInst inst : top.getCellInsts()) { - EDIFPort port = inst.getPort(ab.getClockName()); + EDIFPort port = inst.getPort(ab.getKernelClockName()); if (port != null) { logClkNet.createPortInst(port, inst); } @@ -568,14 +761,31 @@ public static void main(String[] args) { } } - PerformanceExplorer.updateClockPeriodConstraint(array, ab.getClockName(), ab.getClockPeriod()); + PerformanceExplorer.updateClockPeriodConstraint(array, ab.getKernelClockName(), ab.getClockPeriod()); array.setDesignOutOfContext(true); array.setAutoIOBuffers(false); } + Net gndNet = array.getNet(Net.GND_NET); + gndNet.unroute(); + Net vccNet = array.getNet(Net.VCC_NET); + vccNet.unroute(); array.getNetlist().consolidateAllToWorkLibrary(); + + if (ab.getOutOfContext()) { + // Automatically find bounding PBlock based on used Slices, DSPs, and BRAMs + Set usedSites = new HashSet<>(); + for (SiteInst siteInst : array.getSiteInsts()) { + if (isSLICE(siteInst) || isBRAM(siteInst) || isDSP(siteInst)) { + usedSites.add(siteInst.getSite()); + } + } + PBlock pBlock = new PBlock(array.getDevice(), usedSites); + InlineFlopTools.createAndPlaceFlopsInlineOnTopPortsNearPins(array, ab.getTopClockName(), pBlock); + } + t.stop().start("Write DCP"); - array.writeCheckpoint(ab.getOutputName(), CodePerfTracker.SILENT); + array.writeCheckpoint(ab.getOutputName()); t.stop().printSummary(); } diff --git a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java index 7416ff84d..67f0f41a2 100644 --- a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java +++ b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java @@ -22,13 +22,8 @@ package com.xilinx.rapidwright.design.tools; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; import com.xilinx.rapidwright.design.Cell; import com.xilinx.rapidwright.design.Design; @@ -38,16 +33,10 @@ import com.xilinx.rapidwright.design.SitePinInst; import com.xilinx.rapidwright.design.Unisim; import com.xilinx.rapidwright.design.blocks.PBlock; -import com.xilinx.rapidwright.device.BEL; -import com.xilinx.rapidwright.device.Series; -import com.xilinx.rapidwright.device.Site; +import com.xilinx.rapidwright.device.*; import com.xilinx.rapidwright.eco.ECOPlacementHelper; -import com.xilinx.rapidwright.edif.EDIFCell; -import com.xilinx.rapidwright.edif.EDIFCellInst; -import com.xilinx.rapidwright.edif.EDIFHierNet; -import com.xilinx.rapidwright.edif.EDIFNet; -import com.xilinx.rapidwright.edif.EDIFPort; -import com.xilinx.rapidwright.edif.EDIFPortInst; +import com.xilinx.rapidwright.edif.*; +import com.xilinx.rapidwright.placer.blockplacer.Point; import com.xilinx.rapidwright.util.Pair; import com.xilinx.rapidwright.util.StringTools; @@ -56,7 +45,7 @@ * This is targeted at kernel replication preparation prior to routing to ensure * that connections are routed outside of the pblock of the out-of-context * kernel. - * + * */ public class InlineFlopTools { @@ -66,49 +55,106 @@ public class InlineFlopTools { public static final String INLINE_SUFFIX = "_rw_inline_flop"; + private static final int MAX_FFS_PER_SLICE = 5; + private static final Set VALID_CENTROID_SITE_TYPES = + new HashSet<>(Arrays.asList(SiteTypeEnum.SLICEL, SiteTypeEnum.SLICEM)); + /** - * Add flip flops inline on all the top-level ports of an out-of-context design. + * Add flip-flops inline on all the top-level ports of an out-of-context design. * This is useful for out-of-context kernels prior to placement and routing so that * after the flops have been placed, the router is forced to route connections * of each of the ports to each of the flops. This can help alleviate congestion * when the kernels are placed/relocated in context. Note this assumes the * design is not implemented as in most contexts it will be placed and routed * immediately following this modification. - * + * + * @param design The design to modify + * @param clkNet Name of the clock net to use on which to add the flops + * @param keepOut The pblock used to contain the kernel and the added flops will + * not be placed inside this area. + */ + public static void createAndPlaceFlopsInlineOnTopPortsArbitrarily(Design design, String clkNet, PBlock keepOut) { + createAndPlaceFlopsInlineOnTopPorts(design, clkNet, keepOut, false); + } + + /** + * Add flip-flops inline on all the top-level ports of an out-of-context design. + * Flip-flop placements are chosen based on the centroid of net pins for each top-level + * I/O. This is useful for out-of-context kernels prior to final routing so that + * after the flops have been placed, the router is forced to route connections + * of each of the ports to each of the flops. Note this assumes the design is + * placed and potentially partially routed. + * * @param design The design to modify * @param clkNet Name of the clock net to use on which to add the flops * @param keepOut The pblock used to contain the kernel and the added flops will * not be placed inside this area. */ - public static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clkNet, PBlock keepOut) { - assert (design.getSiteInsts().size() == 0); + public static void createAndPlaceFlopsInlineOnTopPortsNearPins(Design design, String clkNet, PBlock keepOut) { + createAndPlaceFlopsInlineOnTopPorts(design, clkNet, keepOut, true); + } + + /** + * Add flip-flops inline on all the top-level ports of an out-of-context design. + * This is useful for out-of-context kernels so that after the flops have been + * placed, the router is forced to route connections of each of the ports to + * each of the flops. This can help alleviate congestion when the kernels are + * placed/relocated in context. + * + * @param design The design to modify + * @param clkNet Name of the clock net to use on which to add the flops + * @param keepOut The pblock used to contain the kernel and the added flops will + * not be placed inside this area. + * @param centroidPlacement Places flip-flops based on the centroid of the top-level net pins. Should only be + * used if a placement already exists. + */ + private static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clkNet, PBlock keepOut, + boolean centroidPlacement) { + assert (design.getSiteInsts().isEmpty()); EDIFCell top = design.getTopEDIFCell(); Site start = keepOut.getAllSites("SLICE").iterator().next(); // TODO this is a bit wasteful boolean exclude = true; Iterator siteItr = ECOPlacementHelper.spiralOutFrom(start, keepOut, exclude).iterator(); siteItr.next(); // Skip the first site, as we are suggesting one inside the pblock - Net clk = design.getNet(clkNet); + EDIFHierNet clk = design.getNetlist().getHierNetFromName(clkNet); Set siteInstsToRoute = new HashSet<>(); for (EDIFPort port : top.getPorts()) { - // Don't flop the clock net if (port.getName().equals(clkNet)) { continue; } if (port.isBus()) { for (int i : port.getBitBlastedIndicies()) { EDIFPortInst inst = port.getInternalPortInstFromIndex(i); - Pair loc = nextAvailPlacement(design, siteItr); - Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); - siteInstsToRoute.add(flop.getSiteInst()); + if (allLeavesAreIBUF(design, inst)) { + continue; + } + + if (centroidPlacement) { + netCentroidFlipFlopPlacement(design, inst, keepOut, clk, siteInstsToRoute); + } else { + Pair loc = nextAvailPlacement(design, siteItr); + Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } } } else { EDIFPortInst inst = port.getInternalPortInst(); - Pair loc = nextAvailPlacement(design, siteItr); - Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); - siteInstsToRoute.add(flop.getSiteInst()); + if (inst != null) { + if (allLeavesAreIBUF(design, inst)) { + continue; + } + + if (centroidPlacement) { + netCentroidFlipFlopPlacement(design, inst, keepOut, clk, siteInstsToRoute); + } else { + Pair loc = nextAvailPlacement(design, siteItr); + Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } + } } } for (SiteInst si : siteInstsToRoute) { @@ -116,20 +162,72 @@ public static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clk } } + private static boolean allLeavesAreIBUF(Design design, EDIFPortInst inst) { + EDIFHierCellInst topInst = design.getNetlist().getTopHierCellInst(); + EDIFHierPortInst hierPortInst = new EDIFHierPortInst(topInst, inst); + List leafHierPortInsts = hierPortInst.getHierarchicalNet().getLeafHierPortInsts(); + + boolean allLeavesAreIBUF = !leafHierPortInsts.isEmpty(); + for (EDIFHierPortInst leafHierPortInst : leafHierPortInsts) { + allLeavesAreIBUF &= leafHierPortInst.getCellType().getName().equals("IBUF"); + } + return allLeavesAreIBUF; + } + + private static void netCentroidFlipFlopPlacement(Design design, EDIFPortInst inst, PBlock keepOut, EDIFHierNet clk, + Set siteInstsToRoute) { + EDIFHierCellInst topInst = design.getNetlist().getTopHierCellInst(); + EDIFHierPortInst hierPortInst = new EDIFHierPortInst(topInst, inst); + List leafHierPortInsts = hierPortInst.getHierarchicalNet().getLeafHierPortInsts(); + List points = new ArrayList<>(); + for (EDIFHierPortInst leafInst : leafHierPortInsts) { + Cell cell = design.getCell(leafInst.getFullHierarchicalInstName()); + if (cell != null && cell.isPlaced()) { + Tile t = cell.getTile(); + Point p = new Point(t.getColumn(), t.getRow()); + points.add(p); + } + } + + if (!points.isEmpty()) { + Site centroid = ECOPlacementHelper.getCentroidOfPoints(design.getDevice(), points, + VALID_CENTROID_SITE_TYPES); + Iterator siteItr = ECOPlacementHelper.spiralOutFrom(centroid, keepOut, true).iterator(); + siteItr.next(); + Pair loc = nextAvailPlacement(design, siteItr); + Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } + } + private static Pair nextAvailPlacement(Design design, Iterator itr) { while (itr.hasNext()) { Site curr = itr.next(); SiteInst candidate = design.getSiteInstFromSite(curr); - if (candidate == null) { - // Empty site, let's use it - return new Pair(curr, curr.getBEL("AFF")); + List usedFFs = new ArrayList<>(); + if (candidate != null) { + for (Cell c : candidate.getCells()) { + if (c.isPlaced() && c.getBEL().isFF() && !c.getBEL().isAnyIMR()) { + usedFFs.add(c.getBEL()); + } + } + } + if (usedFFs.size() < MAX_FFS_PER_SLICE) { + // There is an FF available, use one of them + List bels = Arrays.stream(curr.getBELs()).filter((BEL b) -> b.isFF() && !b.isAnyIMR()) + .collect(Collectors.toList()); + for (BEL b : bels) { + if (!usedFFs.contains(b)) { + return new Pair<>(curr, b); + } + } } } return null; } private static Cell createAndPlaceFlopInlineOnTopPortInst(Design design, EDIFPortInst portInst, Pair loc, - Net clk) { + EDIFHierNet clk) { String name = portInst.getFullName() + INLINE_SUFFIX; Cell flop = design.createAndPlaceCell(design.getTopEDIFCell(), name, Unisim.FDRE, loc.getFirst(), loc.getSecond()); @@ -137,7 +235,13 @@ private static Cell createAndPlaceFlopInlineOnTopPortInst(Design design, EDIFPor net.connect(flop, portInst.isInput() ? "D" : "Q"); design.getGndNet().connect(flop, "R"); design.getVccNet().connect(flop, "CE"); - clk.connect(flop, "C"); + EDIFHierCellInst flopHierCellInst = flop.getEDIFHierCellInst(); + EDIFHierPortInst clkHierPortInst = flopHierCellInst.getPortInst("C"); + if (clkHierPortInst == null) { + clk.getNet().createPortInst("C", flopHierCellInst.getInst()); + clkHierPortInst = flopHierCellInst.getPortInst("C"); + } + EDIFTools.connectPortInstsThruHier(clk, clkHierPortInst, name + "_clk"); EDIFNet origNet = portInst.getNet(); origNet.removePortInst(portInst); net.getLogicalNet().addPortInst(portInst); @@ -157,8 +261,8 @@ private static Cell createAndPlaceFlopInlineOnTopPortInst(Design design, EDIFPor /** * Removes the inline flops added by - * {@link #createAndPlaceFlopsInlineOnTopPorts(Design, String, PBlock)} - * + * {@link #createAndPlaceFlopsInlineOnTopPorts(Design, String, PBlock, boolean)} + * * @param design The current design from which to remove the flops */ public static void removeInlineFlops(Design design) { @@ -168,7 +272,7 @@ public static void removeInlineFlops(Design design) { Net vcc = design.getVccNet(); Set vccPins = new HashSet<>(); pinsToRemove.put(vcc, vccPins); - String[] staticPins = new String[] { "CKEN1", design.getSeries() == Series.Versal ? "RST" : "SRST1" }; + String[] staticPins = new String[]{"CKEN1", design.getSeries() == Series.Versal ? "RST" : "SRST1"}; for (EDIFCellInst inst : design.getTopEDIFCell().getCellInsts()) { if (inst.getName().endsWith(INLINE_SUFFIX)) { Cell flop = design.getCell(inst.getName()); @@ -193,7 +297,7 @@ public static void removeInlineFlops(Design design) { } DesignTools.batchRemoveSitePins(pinsToRemove, true); - String[] ctrlPins = new String[] { "C", "R", "CE" }; + String[] ctrlPins = new String[]{"C", "R", "CE"}; EDIFCell top = design.getTopEDIFCell(); for (EDIFCellInst c : cellsToRemove) { // Remove control set pins @@ -236,11 +340,11 @@ public static void removeInlineFlops(Design design) { public static void main(String[] args) { if (args.length < 3 || args.length > 4) { - System.out.println("USAGE (to add flops) : "+CLK_OPT+"= "+PBLOCK_OPT+"="); + System.out.println("USAGE (to add flops) : " + CLK_OPT + "= " + PBLOCK_OPT + "="); System.out.println("USAGE (to remove flops): " + REMOVE_FLOPS_OPT); return; } - + Design d = Design.readCheckpoint(args[0]); if (args[2].startsWith(CLK_OPT) || args[2].startsWith(PBLOCK_OPT)) { @@ -248,18 +352,17 @@ public static void main(String[] args) { String clkName = StringTools.getOptionValue(CLK_OPT, args); String pblockRange = StringTools.getOptionValue(PBLOCK_OPT, args); if (clkName == null || pblockRange == null) { - throw new RuntimeException("ERROR: Missing value(s) for option(s): " + throw new RuntimeException("ERROR: Missing value(s) for option(s): " + CLK_OPT + "=" + clkName + ", " + PBLOCK_OPT + "=" + pblockRange); } PBlock pblock = new PBlock(d.getDevice(), pblockRange); - createAndPlaceFlopsInlineOnTopPorts(d, clkName, pblock); + createAndPlaceFlopsInlineOnTopPortsArbitrarily(d, clkName, pblock); } else if (args[2].equals(REMOVE_FLOPS_OPT)) { removeInlineFlops(d); } else { - System.err.println("ERROR: Unrecognized option '" + args[2] +"'"); + System.err.println("ERROR: Unrecognized option '" + args[2] + "'"); } - - + d.writeCheckpoint(args[1]); } } diff --git a/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java b/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java index c978637df..8f125e016 100644 --- a/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java +++ b/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java @@ -272,7 +272,7 @@ public static Site getCentroidOfPoints(Device device, List points, Set targetSiteTypes) for (SitePinInst i : net.getPins()) { points.add(new Point(i.getTile().getColumn(), i.getTile().getRow())); } - return ECOPlacementHelper.getCentroidOfPoints(net.getSource().getTile().getDevice(), points, targetSiteTypes); + return ECOPlacementHelper.getCentroidOfPoints(net.getDesign().getDevice(), points, targetSiteTypes); } /** diff --git a/src/com/xilinx/rapidwright/edif/EDIFPort.java b/src/com/xilinx/rapidwright/edif/EDIFPort.java index ea30adf89..632877bf0 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFPort.java +++ b/src/com/xilinx/rapidwright/edif/EDIFPort.java @@ -256,7 +256,7 @@ public EDIFPortInst getInternalPortInstFromIndex(int index) { public EDIFPortInst getInternalPortInst() { assert (!isBus()); EDIFNet net = getInternalNet(); - return net.getPortInst(null, getBusName()); + return net == null ? null : net.getPortInst(null, getBusName()); } /** diff --git a/src/com/xilinx/rapidwright/util/PerformanceExplorer.java b/src/com/xilinx/rapidwright/util/PerformanceExplorer.java index 8ddebc66a..89f53ba5b 100644 --- a/src/com/xilinx/rapidwright/util/PerformanceExplorer.java +++ b/src/com/xilinx/rapidwright/util/PerformanceExplorer.java @@ -32,18 +32,14 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; import com.xilinx.rapidwright.design.ConstraintGroup; import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.blocks.PBlock; +import com.xilinx.rapidwright.design.tools.InlineFlopTools; import joptsimple.OptionParser; import joptsimple.OptionSet; @@ -55,8 +51,11 @@ public class PerformanceExplorer { private static final String INITIAL_DCP_NAME = "initial.dcp"; + private static final String INITIAL_ENCRYPTED_TCL_NAME = "initial_load.tcl"; private static final String PLACED_TIMING_RESULT = "place_timing.twr"; private static final String ROUTED_TIMING_RESULT = "route_timing.twr"; + private static final String ROUTED_TIMING_SUMMARY = "route_timing_summary.rpt"; + private static final String ROUTE_STATUS_RESULT = "route_status.rpt"; private static final String RUN_TCL_NAME = "run.tcl"; private static final double DEFAULT_MIN_CLK_UNCERT = -0.100; private static final double DEFAULT_MAX_CLK_UNCERT = 0.250; @@ -86,6 +85,8 @@ public class PerformanceExplorer { private boolean addEDIFAndMetadata; + private boolean getBestPerPBlock; + private ArrayList placerDirectives; private ArrayList routerDirectives; @@ -102,6 +103,8 @@ public class PerformanceExplorer { private boolean reusePreviousResults; + private boolean ensureExternalRoutability; + public PerformanceExplorer(Design d, String testDir, String clkName, double targetPeriod) { init(d, testDir, clkName, targetPeriod, null); } @@ -269,6 +272,22 @@ public void setAddEDIFAndMetadata(boolean addEDIFAndMetadata) { this.addEDIFAndMetadata = addEDIFAndMetadata; } + public boolean getBestPerPBlock() { + return getBestPerPBlock; + } + + public void setGetBestPerPBlock(boolean getBestPerPBlock) { + this.getBestPerPBlock = getBestPerPBlock; + } + + public boolean ensureExternalRoutability() { + return ensureExternalRoutability; + } + + public void setEnsureExternalRoutability(boolean ensureExternalRoutability) { + this.ensureExternalRoutability = ensureExternalRoutability; + } + public PBlock getPBlock(int i) { return pblockLookup[i]; } @@ -282,11 +301,16 @@ public boolean reusePreviousResults() { } public ArrayList createTclScript(String initialDcp, String instDirectory, - PlacerDirective p, RouterDirective r, String clockUncertainty, Entry pblockEntry) { + PlacerDirective p, RouterDirective r, String clockUncertainty, + Entry pblockEntry, String encryptedTcl) { PBlock pblock = pblockEntry.getKey(); String pblockCells = pblockEntry.getValue(); ArrayList lines = new ArrayList<>(); - lines.add("open_checkpoint " + initialDcp); + if (encryptedTcl == null) { + lines.add("open_checkpoint " + initialDcp); + } else { + lines.add("source " + encryptedTcl); + } lines.add("set_clock_uncertainty -setup "+clockUncertainty+" [get_clocks "+clkName+"]"); if (pblock != null) { String pblockName = pblock.getName() == null ? "pe_pblock_1" : pblock.getName(); @@ -298,7 +322,13 @@ public ArrayList createTclScript(String initialDcp, String instDirectory lines.add("set_property CONTAIN_ROUTING 1 [get_pblocks "+ pblockName+"]"); } } - lines.add("place_design -unplace"); + if (ensureExternalRoutability()) { + lines.add("lock_design -level placement"); + } + lines.add("opt_design"); + if (!ensureExternalRoutability()) { + lines.add("place_design -unplace"); + } lines.add("place_design -directive " + p.name()); lines.add("set_clock_uncertainty -setup 0.0 [get_clocks "+clkName+"]"); lines.add("report_timing -file "+instDirectory + File.separator+PLACED_TIMING_RESULT); @@ -345,6 +375,7 @@ public void explorePerformance() { FileTools.makeDirs(runDirectory); runDirectory = new File(runDirectory).getAbsolutePath(); String dcpName = runDirectory + File.separator + INITIAL_DCP_NAME; + updateClockPeriodConstraint(design, getClkName(), getTargetPeriod()); design.writeCheckpoint(dcpName); @@ -361,6 +392,14 @@ public void explorePerformance() { pblockLookup = new PBlock[pblocks.size()]; for (Entry e : pblocks.entrySet()) { PBlock pblock = e.getKey(); + + String pblockDcpName = dcpName; + if (ensureExternalRoutability()) { + InlineFlopTools.createAndPlaceFlopsInlineOnTopPortsArbitrarily(design, clkName, pblock); + pblockDcpName = runDirectory + File.separator + "pblock" + pb + "_" + INITIAL_DCP_NAME; + design.writeCheckpoint(pblockDcpName); + } + for (PlacerDirective p : getPlacerDirectives()) { for (RouterDirective r : getRouterDirectives()) { for (double c : getClockUncertaintyValues()) { @@ -372,7 +411,13 @@ public void explorePerformance() { System.out.println(uniqueID); String instDir = runDirectory + File.separator + uniqueID; FileTools.makeDir(instDir); - ArrayList tcl = createTclScript(dcpName, instDir, p, r, roundedC, e); + String encryptedTcl = null; + boolean encrypted = !design.getNetlist().getEncryptedCells().isEmpty(); + if (encrypted) { + encryptedTcl = runDirectory + File.separator + "pblock" + pb + + "_" + INITIAL_ENCRYPTED_TCL_NAME; + } + ArrayList tcl = createTclScript(pblockDcpName, instDir, p, r, roundedC, e, encryptedTcl); String scriptName = instDir + File.separator + RUN_TCL_NAME; if (!reusePreviousResults) { @@ -448,6 +493,29 @@ public List> getBestResultsPerPBlock() { return results; } + public void getBestDesignPerPBlock() { + List> bestResultsPerPBlock = getBestResultsPerPBlock(); + Path runDir = Paths.get(runDirectory); + + int pblockNum = 0; + for (Pair best : bestResultsPerPBlock) { + String pblock = "pblock" + pblockNum; + Path bestPath = best.getFirst(); + Float bestSlack = best.getSecond(); + if (bestSlack < 0.0) { + System.out.println("No implementation passes timing for " + pblock); + } + + Design d = Design.readCheckpoint(bestPath + File.separator + "routed.dcp"); + + if (ensureExternalRoutability()) { + InlineFlopTools.removeInlineFlops(d); + } + + d.writeCheckpoint(runDirectory + File.separator + pblock + "_best.dcp"); + } + } + private static final String INPUT_DCP_OPT = "i"; private static final String PBLOCK_FILE_OPT = "b"; private static final String CLK_NAME_OPT = "c"; @@ -464,6 +532,9 @@ public List> getBestResultsPerPBlock() { private static final String RUN_DIR_OPT = "d"; private static final String VIVADO_PATH_OPT = "y"; private static final String MAX_CONCURRENT_JOBS_OPT = "z"; + private static final String ENSURE_EXT_ROUTABILITY = "e"; + private static final String COLLECT_RESULTS_OPT = "o"; + private static final String REUSE_PREVIOUS_RESULTS = "reuse-previous"; private static OptionParser createOptionParser() { // Defaults @@ -485,6 +556,9 @@ private static OptionParser createOptionParser() { accepts(VIVADO_PATH_OPT).withOptionalArg().defaultsTo(DEFAULT_VIVADO).describedAs("Specifies vivado path"); accepts(CONTAIN_ROUTING_OPT).withOptionalArg().ofType(Boolean.class).defaultsTo(DEFAULT_CONTAIN_ROUTING).describedAs("Sets attribute on pblock to contain routing"); accepts(MAX_CONCURRENT_JOBS_OPT).withOptionalArg().ofType(Integer.class).defaultsTo(JobQueue.MAX_LOCAL_CONCURRENT_JOBS).describedAs("Max number of concurrent job when run locally"); + accepts(ENSURE_EXT_ROUTABILITY, "Ensure all I/O are routable outside the pblock"); + accepts(COLLECT_RESULTS_OPT,"Collect results into output csv"); + accepts(REUSE_PREVIOUS_RESULTS, "Reuse previous results if they exist"); acceptsAll( Arrays.asList(HELP_OPT, "?"), "Print Help" ).forHelp(); }}; @@ -548,7 +622,9 @@ public static void main(String[] args) { pe.setVivadoPath((String)opts.valueOf(VIVADO_PATH_OPT)); pe.setContainRouting((boolean)opts.valueOf(CONTAIN_ROUTING_OPT)); pe.setAddEDIFAndMetadata((boolean)opts.valueOf(ADD_EDIF_METADATA_OPT)); - + pe.setGetBestPerPBlock(opts.has(COLLECT_RESULTS_OPT)); + pe.setReusePreviousResults(opts.has(REUSE_PREVIOUS_RESULTS)); + pe.setEnsureExternalRoutability(opts.has(ENSURE_EXT_ROUTABILITY)); if (opts.hasArgument(PBLOCK_FILE_OPT)) { String fileName = (String) opts.valueOf(PBLOCK_FILE_OPT); @@ -572,5 +648,9 @@ public static void main(String[] args) { } pe.explorePerformance(); + + if (pe.getBestPerPBlock) { + pe.getBestDesignPerPBlock(); + } } } From 2caaf528aa4a2ecb9fc0f5ef7d776b30da253047 Mon Sep 17 00:00:00 2001 From: Andrew Butt Date: Mon, 20 Oct 2025 17:51:40 -0600 Subject: [PATCH 17/24] Fix npe when no top dcp provided (#1301) Signed-off-by: Andrew Butt --- src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java index 4b03a42c5..37758db8c 100644 --- a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java @@ -407,7 +407,7 @@ private void initializeArrayBuilder(OptionSet options) { if (options.has(TOP_CLK_NAME_OPTS.get(0))) { setTopClockName(((String) options.valueOf(TOP_CLK_NAME_OPTS.get(0)))); - } else { + } else if (options.has(TOP_LEVEL_DESIGN_OPTS.get(0))) { setTopClockName(ClockTools.getClockFromDesign(getTopDesign()).toString()); } From bd16cc07984fdc7f5af717051ca8ad0009e14a07 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Fri, 31 Oct 2025 16:57:32 -0600 Subject: [PATCH 18/24] Add or-tools 9.14.6206, update protobuf to 4.31.1, update commons-io to 2.20.0 (#1306) * Testing updated 3rd party packages Signed-off-by: Chris Lavin * Minor additions to DesignComparator Signed-off-by: Chris Lavin * Update license to reflect new/updated packages Signed-off-by: Chris Lavin * rc1 jar Signed-off-by: Chris Lavin * Fix failing DesignComparator test. Signed-off-by: Chris Lavin * Address review comments Signed-off-by: Chris Lavin --------- Signed-off-by: Chris Lavin --- .classpath | 8 ++--- .github/workflows/build.yml | 2 +- LICENSE.TXT | 13 ++++--- common.gradle | 5 +-- .../design/compare/DesignComparator.java | 8 +++++ .../design/compare/DesignDiffType.java | 6 ++-- .../design/compare/TestDesignComparator.java | 34 +++++++++++++------ 7 files changed, 51 insertions(+), 25 deletions(-) diff --git a/.classpath b/.classpath index feb64e603..8a2cfcd9c 100644 --- a/.classpath +++ b/.classpath @@ -17,7 +17,7 @@ - + @@ -33,9 +33,9 @@ - + - + @@ -49,7 +49,7 @@ - + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5c3a7cc89..c9d544eae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: pull_request: env: - RAPIDWRIGHT_VERSION: v2025.1.3-beta + RAPIDWRIGHT_VERSION: v2025.2.0-rc1-beta jobs: build: diff --git a/LICENSE.TXT b/LICENSE.TXT index 62ea37d39..fd47464c5 100755 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -20,7 +20,7 @@ for details) RapidWright (Apache 2.0) - JOpt Simple (MIT License) - - Protocol Buffers 3.25.0 (Protocol Buffer License) + - Protocol Buffers 4.31.1 (Protocol Buffer License) - Kryo 5.2.1 (BSD 3-clause "New" or "Revised" License) - MinLog 1.3.1 (BSD 3-clause "New" or "Revised" License) - Reflectasm 1.11.9 (BSD 3-clause "New" or "Revised" License) @@ -101,9 +101,11 @@ RapidWright (Apache 2.0) - Opentest4j 1.2.0 (Apache 2.0) - JetBrains Java Annotations 20.1.0 (Apache 2.0) - jzlib 1.1.3 (BSD-style License) - - Apache Commons IO 2.11.0 (Apache 2.0) + - Apache Commons IO 2.20.0 (Apache 2.0) - Zstd-jni 1.5.5-1 (2-clause BSD) - Zstandard 1.5.5 (3-clause BSD or GPLv2) + - Google or-tools-java 9.14.6206 (Apache 2.0) + - Java Native Access jna-5.14.0 (Apache 2.0) @@ -429,7 +431,7 @@ IF LICENSEE DOES NOT AGREE TO ALL OF THE TERMS AND CONDITIONS OF THIS AGREEMENT, ************************ END LICENSE ************************ -**************************google-protobuf v3.25.0 - BSD-3-Clause*************************** +**************************google-protobuf v4.31.1 - BSD-3-Clause*************************** Copyright 2008 Google Inc. All rights reserved. @@ -8339,11 +8341,12 @@ Copyright 2013-2015 Sandstorm Development Group Inc. and contributors - Apiguardian API 1.1.0 (Apache 2.0) - Opentest4j 1.2.0 (Apache 2.0) - JetBrains Java Annotations 20.1.0 (Apache 2.0) - - Apache Commons IO 2.11.0 + - Apache Commons IO 2.20.0 *** Gradle Wrapper 7.4.2, Apiguardian API 1.1.0, Opentest4j 1.2.0, - JetBrains Java Annotations 20.1.0, Apache Commons IO 2.11.0 (Apache 2.0) **************************** + JetBrains Java Annotations 20.1.0, Apache Commons IO 2.20.0, + Google or-tools-java 9.14.6206, Java Native Access jna-5.14.0 (Apache 2.0) **************************** Apache License Version 2.0, January 2004 diff --git a/common.gradle b/common.gradle index 97c0300d1..7d65de368 100644 --- a/common.gradle +++ b/common.gradle @@ -38,13 +38,14 @@ dependencies { api 'org.capnproto:runtime:0.1.13' api 'net.sf.jopt-simple:jopt-simple:5.0.4' api 'org.python:jython-standalone:2.7.2' - api 'com.google.protobuf:protobuf-java:3.25.0' + api 'com.google.ortools:ortools-java:9.14.6206' + api 'com.google.protobuf:protobuf-java:4.31.1' api 'org.jetbrains:annotations:20.1.0' api 'org.zeromq:jeromq:0.5.2' api 'commons-cli:commons-cli:1.2' api 'org.json:json:20160810' api 'com.jcraft:jzlib:1.1.3' - api 'commons-io:commons-io:2.11.0' + 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' testFixturesApi 'org.junit.jupiter:junit-jupiter-api:5.7.1' diff --git a/src/com/xilinx/rapidwright/design/compare/DesignComparator.java b/src/com/xilinx/rapidwright/design/compare/DesignComparator.java index 290687c40..a850a487e 100644 --- a/src/com/xilinx/rapidwright/design/compare/DesignComparator.java +++ b/src/com/xilinx/rapidwright/design/compare/DesignComparator.java @@ -270,6 +270,14 @@ public int compareSiteInsts(SiteInst gold, SiteInst test) { if (!Objects.equals(e.getValue().getType(), testCell.getType())) { addDiff(DesignDiffType.PLACED_CELL_TYPE, e.getValue(), testCell, gold, ""); } + + if (!Objects.equals(e.getValue().isBELFixed(), testCell.isBELFixed())) { + addDiff(DesignDiffType.PLACED_CELL_IS_BEL_FIXED, e.getValue(), testCell, gold, ""); + } + + if (!Objects.equals(e.getValue().isSiteFixed(), testCell.isSiteFixed())) { + addDiff(DesignDiffType.PLACED_CELL_IS_SITE_FIXED, e.getValue(), testCell, gold, ""); + } } for (Entry e : testMap.entrySet()) { Cell extraCell = e.getValue(); diff --git a/src/com/xilinx/rapidwright/design/compare/DesignDiffType.java b/src/com/xilinx/rapidwright/design/compare/DesignDiffType.java index 6ad0393b5..5d2b73662 100644 --- a/src/com/xilinx/rapidwright/design/compare/DesignDiffType.java +++ b/src/com/xilinx/rapidwright/design/compare/DesignDiffType.java @@ -39,6 +39,8 @@ public enum DesignDiffType { PLACED_CELL_EXTRA, PLACED_CELL_TYPE, PLACED_CELL_NAME, + PLACED_CELL_IS_BEL_FIXED, + PLACED_CELL_IS_SITE_FIXED, SITEPIP_MISSING, SITEPIP_EXTRA, SITEPIP_INPIN_NAME, @@ -49,7 +51,7 @@ public enum DesignDiffType { NET_EXTRA, PIP_MISSING, PIP_EXTRA, - PIP_FLAGS; + PIP_FLAGS; private boolean isMissingType; @@ -63,7 +65,7 @@ public enum DesignDiffType { private static Set siteInstParentTypes = EnumSet.of(SITEINST_TYPE, PLACED_CELL_MISSING, PLACED_CELL_EXTRA, PLACED_CELL_TYPE, PLACED_CELL_NAME, SITEPIP_MISSING, SITEPIP_EXTRA, SITEPIP_INPIN_NAME, SITEWIRE_NET_MISSING, SITEWIRE_NET_EXTRA, - SITEWIRE_NET_NAME); + SITEWIRE_NET_NAME, PLACED_CELL_IS_BEL_FIXED, PLACED_CELL_IS_SITE_FIXED); private static Set netParentTypes = EnumSet.of(PIP_EXTRA, PIP_FLAGS, PIP_MISSING); diff --git a/test/src/com/xilinx/rapidwright/design/compare/TestDesignComparator.java b/test/src/com/xilinx/rapidwright/design/compare/TestDesignComparator.java index 61c50208b..be2cb09d1 100644 --- a/test/src/com/xilinx/rapidwright/design/compare/TestDesignComparator.java +++ b/test/src/com/xilinx/rapidwright/design/compare/TestDesignComparator.java @@ -109,12 +109,24 @@ public void testDesignComparator() { String oldType = removedCell.getType(); removedCell.setType("MISMATCHEDTYPE"); - compareDesign(4, 1, DesignDiffType.PLACED_CELL_TYPE, dc, gold, test2); + compareDesign(6, 1, DesignDiffType.PLACED_CELL_TYPE, dc, gold, test2); removedCell.setType(oldType); removedCell.updateName("NEWNAME"); - compareDesign(4, 1, DesignDiffType.PLACED_CELL_NAME, dc, gold, test2); + compareDesign(6, 1, DesignDiffType.PLACED_CELL_NAME, dc, gold, test2); + + Cell compareCell = siteInst.getCell("F6LUT"); + + Assertions.assertFalse(compareCell.isBELFixed()); + compareCell.setBELFixed(true); + + compareDesign(7, 2, DesignDiffType.PLACED_CELL_IS_BEL_FIXED, dc, gold, test2); + + Assertions.assertFalse(compareCell.isSiteFixed()); + compareCell.setSiteFixed(true); + + compareDesign(8, 2, DesignDiffType.PLACED_CELL_IS_SITE_FIXED, dc, gold, test2); SitePIP sitePIP = siteInst.getUsedSitePIP("CLKINV"); Net clk = siteInst.getNetFromSiteWire(sitePIP.getInputPin().getSiteWireName()); @@ -122,11 +134,11 @@ public void testDesignComparator() { siteInst.routeIntraSiteNet(clk, sitePIP.getInputPin(), sitePIP.getInputPin()); siteInst.routeIntraSiteNet(clk, sitePIP.getOutputPin(), sitePIP.getOutputPin()); - compareDesign(5, 1, DesignDiffType.SITEPIP_MISSING, dc, gold, test2); + compareDesign(9, 1, DesignDiffType.SITEPIP_MISSING, dc, gold, test2); siteInst.addSitePIP("FFMUXC1", "D6"); - compareDesign(6, 1, DesignDiffType.SITEPIP_EXTRA, dc, gold, test2); + compareDesign(10, 1, DesignDiffType.SITEPIP_EXTRA, dc, gold, test2); sitePIP = siteInst.getUsedSitePIP("FFMUXA1"); Net net = siteInst.getNetFromSiteWire(sitePIP.getInputPin().getSiteWireName()); @@ -135,21 +147,21 @@ public void testDesignComparator() { siteInst.routeIntraSiteNet(net, sitePIP.getOutputPin(), sitePIP.getOutputPin()); siteInst.addSitePIP("FFMUXA1", "D6"); - compareDesign(8, 1, DesignDiffType.SITEPIP_INPIN_NAME, dc, gold, test2); + compareDesign(12, 1, DesignDiffType.SITEPIP_INPIN_NAME, dc, gold, test2); Assertions.assertEquals(1, dc.getDiffList(DesignDiffType.SITEWIRE_NET_EXTRA).size()); siteInst.unrouteIntraSiteNet(sitePIP.getInputPin(), sitePIP.getInputPin()); - compareDesign(9, 1, DesignDiffType.SITEWIRE_NET_MISSING, dc, gold, test2); + compareDesign(13, 1, DesignDiffType.SITEWIRE_NET_MISSING, dc, gold, test2); siteInst.routeIntraSiteNet(new Net("mismatch"), sitePIP.getInputPin(), sitePIP.getInputPin()); - compareDesign(9, 1, DesignDiffType.SITEWIRE_NET_NAME, dc, gold, test2); + compareDesign(13, 1, DesignDiffType.SITEWIRE_NET_NAME, dc, gold, test2); Assertions.assertTrue(net.hasPIPs()); test2.removeNet(net); - compareDesign(28, 1, DesignDiffType.NET_MISSING, dc, gold, test2); + compareDesign(32, 1, DesignDiffType.NET_MISSING, dc, gold, test2); Net extra = new Net("extraNet"); for (PIP p : net.getPIPs()) { @@ -157,17 +169,17 @@ public void testDesignComparator() { } test2.addNet(extra); - compareDesign(29, 1, DesignDiffType.NET_EXTRA, dc, gold, test2); + compareDesign(33, 1, DesignDiffType.NET_EXTRA, dc, gold, test2); Assertions.assertTrue(extra.hasPIPs()); clk.addPIP(extra.getPIPs().get(0)); - compareDesign(30, 1, DesignDiffType.PIP_EXTRA, dc, gold, test2); + compareDesign(34, 1, DesignDiffType.PIP_EXTRA, dc, gold, test2); clk.getPIPs().remove(clk.getPIPs().size() - 1); clk.getPIPs().remove(clk.getPIPs().size() - 1); - compareDesign(30, 1, DesignDiffType.PIP_MISSING, dc, gold, test2); + compareDesign(34, 1, DesignDiffType.PIP_MISSING, dc, gold, test2); if (dc.comparePIPFlags()) { clk.getPIPs().get(clk.getPIPs().size() - 1).setIsStub(true); From 392e780f9a270ded35ce44d62873f5fcecf35f92 Mon Sep 17 00:00:00 2001 From: eddieh-xlnx Date: Fri, 31 Oct 2025 18:18:11 -0700 Subject: [PATCH 19/24] [ECOTools] disconnectNet() to use skipUnrouteIntraSite property (#1300) * [ECOTools] disconnectNet() to use skipUnrouteIntraSite property Full property: rapidwright.ecotools.disconnectNet.skipUnrouteIntraSite Signed-off-by: Eddie Hung * Fix comment Signed-off-by: Eddie Hung * [TestCell] Add testGetAllCorrespondingSitePinNamesLUTRouteThru() Signed-off-by: Eddie Hung --------- Signed-off-by: Eddie Hung --- src/com/xilinx/rapidwright/eco/ECOTools.java | 22 +++++++++++++----- .../xilinx/rapidwright/design/TestCell.java | 23 +++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/com/xilinx/rapidwright/eco/ECOTools.java b/src/com/xilinx/rapidwright/eco/ECOTools.java index c8c1ca55f..a5a0b600a 100644 --- a/src/com/xilinx/rapidwright/eco/ECOTools.java +++ b/src/com/xilinx/rapidwright/eco/ECOTools.java @@ -60,6 +60,7 @@ import com.xilinx.rapidwright.edif.EDIFTools; import com.xilinx.rapidwright.rwroute.RouterHelper; import com.xilinx.rapidwright.util.Pair; +import com.xilinx.rapidwright.util.Params; /** * A collection of methods for performing ECO operations. @@ -117,10 +118,16 @@ public static void disconnectNet(Design design, * if this method is called many times as the process is expensive * without batching. This map can also allow SitePinInst objects to be * reused by {@link #connectNet(Design, Map, Map)}. + * By default, this method will unroute any intra-site routing associated with the disconnected + * pin. The Java property "rapidwright.ecotools.disconnectNet.skipUnrouteIntraSite" disables this + * behaviour which can be helpful for when disconnectNet() is followed by connectNet() that + * will re-use this intra-site routing. */ public static void disconnectNet(Design design, List pins, Map> deferredRemovals) { + final boolean unrouteIntraSite = !Params.isParamSet("rapidwright.ecotools.disconnectNet.skipUnrouteIntraSite"); + for (EDIFHierPortInst ehpi : pins) { EDIFHierNet ehn = ehpi.getHierarchicalNet(); List leafPortInsts; @@ -165,7 +172,7 @@ public static void disconnectNet(Design design, } if (sourcePresent) { - // Downstream does contains a source, remove everything upstream instead + // Downstream does contain a source, remove everything upstream instead visitedNets.clear(); visitedNets.add(internalEhn); leafPortInsts = ehn.getLeafHierPortInsts(false, visitedNets); @@ -196,11 +203,14 @@ public static void disconnectNet(Design design, // SPI also services a different logical port inst; skip continue; } - BELPin otherPin = cell.getBELPin(leafEhpi); - BELPin src = otherPin.isOutput() ? otherPin : spi.getBELPin(); - BELPin snk = otherPin.isOutput() ? spi.getBELPin() : otherPin; - cell.getSiteInst().unrouteIntraSiteNet(src, snk); + if (unrouteIntraSite) { + BELPin otherPin = cell.getBELPin(leafEhpi); + BELPin src = otherPin.isOutput() ? otherPin : spi.getBELPin(); + BELPin snk = otherPin.isOutput() ? spi.getBELPin() : otherPin; + cell.getSiteInst().unrouteIntraSiteNet(src, snk); + } + DesignTools.handlePinRemovals(spi, deferredRemovals); } } @@ -465,7 +475,7 @@ public static void connectNet(Design design, String message = "Site pin " + spi.getSitePinName() + " cannot be used " + "to connect to logical pin '" + ehpi + "' since it is also connected to pin '" + otherEhpi + "'."; - String warnIfCellInstStartsWith = System.getProperty("rapidwright.ecotools.connectNet.warnIfCellInstStartsWith"); + String warnIfCellInstStartsWith = Params.getParamValue("rapidwright.ecotools.connectNet.warnIfCellInstStartsWith"); String cellInstName = (warnIfCellInstStartsWith != null) ? otherEhpi.getPortInst().getCellInst().getName() : null; if (cellInstName != null && cellInstName.startsWith(warnIfCellInstStartsWith)) { System.err.println("WARNING: " + message); diff --git a/test/src/com/xilinx/rapidwright/design/TestCell.java b/test/src/com/xilinx/rapidwright/design/TestCell.java index 52fa4cf89..1319be876 100644 --- a/test/src/com/xilinx/rapidwright/design/TestCell.java +++ b/test/src/com/xilinx/rapidwright/design/TestCell.java @@ -66,6 +66,29 @@ public void testGetAllCorrespondingSitePinNames(String deviceName, Assertions.assertEquals(expectedSitePins, sitePinNames.toString()); } + @ParameterizedTest + @CsvSource({ + "false,[G5]", + "true,'[G5, G1, G2, G3, G4, G6]'", + }) + public void testGetAllCorrespondingSitePinNamesLUTRouteThru(boolean considerLutRoutethru, String expectedSitePins) { + Design d = new Design("testGetAllCorrespondingSitePinNamesLUTRouteThru", Device.KCU105); + SiteInst si = d.createSiteInst(d.getDevice().getSite("SLICE_X32Y73")); + Cell cell = d.createAndPlaceCell("f7mux", Unisim.MUXF7, si.getSiteName() + "/F7MUX_GH"); + + Net netS = d.createNet("netS"); + Assertions.assertTrue(si.routeIntraSiteNet(netS, si.getBELPin("GX", "GX"), + si.getBELPin("F7MUX_GH", "S0"))); + List sitePinNames = cell.getAllCorrespondingSitePinNames("S", considerLutRoutethru); + Assertions.assertEquals("[GX]", sitePinNames.toString()); + + Net net1 = d.createNet("net1"); + Assertions.assertTrue(si.routeIntraSiteNet(net1, si.getBELPin("G5", "G5"), + si.getBELPin("F7MUX_GH", "1"))); + sitePinNames = cell.getAllCorrespondingSitePinNames("I1", considerLutRoutethru); + Assertions.assertEquals(expectedSitePins, sitePinNames.toString()); + } + @ParameterizedTest @CsvSource({ // Versal input pins From 3d0bf93ef1d14cb97e1f94ddb93dde413e63bc54 Mon Sep 17 00:00:00 2001 From: Jakob Wenzel Date: Tue, 11 Nov 2025 03:54:25 +0100 Subject: [PATCH 20/24] Full TCL parser for XDC (#1265) * Full TCL parser for XDC Signed-off-by: Jakob Wenzel * Testing updated 3rd party packages Signed-off-by: Chris Lavin * Minor additions to DesignComparator Signed-off-by: Chris Lavin * Update license to reflect new/updated packages Signed-off-by: Chris Lavin * rc1 jar Signed-off-by: Chris Lavin * Fix failing DesignComparator test. Signed-off-by: Chris Lavin * Adds Jacl 1.4.1 as a library dependency Signed-off-by: Chris Lavin * Address review comments Signed-off-by: Chris Lavin * add more jacadoc, refactor to own package Signed-off-by: Jakob Wenzel * 2025.2.0-rc2 jar with refactored XDCParser reference in Design.writeCheckpoint() Signed-off-by: Chris Lavin * Add vertical clock spine printing and clock root detection (#1314) * Add vertical clock spine printing and clock root detection Signed-off-by: Andrew Butt * Update comments Signed-off-by: Andrew Butt * Add test for findClockRootVRoute Signed-off-by: Andrew Butt * Update test/src/com/xilinx/rapidwright/design/TestNetTools.java Co-authored-by: Chris Lavin Signed-off-by: Andrew Butt * Update test/src/com/xilinx/rapidwright/design/TestNetTools.java Co-authored-by: Chris Lavin Signed-off-by: Andrew Butt --------- Signed-off-by: Andrew Butt Signed-off-by: Andrew Butt Co-authored-by: Chris Lavin * Apply suggestions from code review Co-authored-by: Chris Lavin Signed-off-by: Jakob Wenzel * [PBlock] Add IsSoft and ExcludePlacement property in pblock (#1315) * [PBlock] Add IsSoft and ExcludePlacement property in pblock Signed-off-by: coherent17 * [PBlock] Update getTclConstraints Signed-off-by: coherent17 * [PBlock] reuse dcp in RapidWrightDCP to run test Signed-off-by: coherent17 * [PblockProperty] Relocate PblockProperty and add unit test to test TclConstraints Signed-off-by: coherent17 * [PblockProperty] Remove toString in PblockProperty Signed-off-by: coherent17 --------- Signed-off-by: coherent17 * apply more review suggestions Signed-off-by: Jakob Wenzel * link to class documentation for lookup Signed-off-by: Jakob Wenzel * add testcase Signed-off-by: Jakob Wenzel * Test constraints for parsing, stringifying and then parsing again Signed-off-by: Jakob Wenzel * add missing license header Signed-off-by: Jakob Wenzel * parse pblocks with XDCParser Signed-off-by: Jakob Wenzel * fix wrong import, fix license headers Signed-off-by: Jakob Wenzel * check XDCParser against all DCPs in RapidWrightDCP Signed-off-by: Jakob Wenzel * 2025.2.0-rc3, fixes #1320 Signed-off-by: Chris Lavin * add roundtrip testing to TestConstraintTools Signed-off-by: Jakob Wenzel * test roundtrip for all xdc, speed up get_cells Signed-off-by: Jakob Wenzel * Updating reference to RapidWrightDCP Signed-off-by: Chris Lavin --------- Signed-off-by: Jakob Wenzel Signed-off-by: Chris Lavin Signed-off-by: Andrew Butt Signed-off-by: Andrew Butt Signed-off-by: coherent17 Co-authored-by: Chris Lavin Co-authored-by: Andrew Butt Co-authored-by: Chris Lavin Co-authored-by: Coherent17 --- .classpath | 5 +- .github/workflows/build.yml | 3 +- LICENSE.TXT | 212 ++++ common.gradle | 1 + .../xilinx/rapidwright/design/NetTools.java | 198 ++- .../rapidwright/design/blocks/PBlock.java | 32 +- .../design/blocks/PBlockRange.java | 9 + .../design/blocks/PblockProperty.java | 34 + .../design/xdc/ClockConstraint.java | 75 ++ .../rapidwright/design/xdc/Constraint.java | 26 + .../design/xdc/ConstraintTools.java | 18 +- .../design/xdc/PBlockConstraint.java | 61 + .../design/xdc/PackagePinConstraint.java | 108 ++ .../xdc/UnsupportedConstraintElement.java | 331 +++++ .../design/xdc/XDCConstraints.java | 181 +++ .../rapidwright/design/xdc/XDCParser.java | 265 ++++ .../xdc/parser/AddCellsToPblockCommand.java | 99 ++ .../design/xdc/parser/CellObject.java | 103 ++ .../design/xdc/parser/CreateClockCommand.java | 99 ++ .../xdc/parser/CreatePBlockCommand.java | 55 + .../design/xdc/parser/DebugDumpCommand.java | 71 ++ .../design/xdc/parser/DesignObject.java | 136 +++ .../design/xdc/parser/DumpObjsCommand.java | 53 + .../design/xdc/parser/EdifCellLookup.java | 166 +++ .../design/xdc/parser/GetCellsCommand.java | 310 +++++ .../design/xdc/parser/NameDesignObject.java | 111 ++ .../design/xdc/parser/ObjType.java | 41 + .../xdc/parser/ObjectGetterCommand.java | 77 ++ .../xdc/parser/RegularEdifCellLookup.java | 114 ++ .../xdc/parser/ResizePBlockCommand.java | 78 ++ .../design/xdc/parser/SetPropertyCommand.java | 182 +++ .../xdc/parser/TclHashIdentifiedObject.java | 90 ++ .../xdc/parser/UnsupportedCmdResult.java | 141 +++ .../xdc/parser/UnsupportedGetterCommand.java | 87 ++ .../xdc/parser/UnsupportedIfCommand.java | 249 ++++ .../xdc/parser/UnsupportedSetterCommand.java | 68 ++ .../design/xdc/parser/XDCTools.java | 35 + .../interchange/DcpToInterchange.java | 2 +- .../xilinx/rapidwright/ipi/BlockStitcher.java | 13 +- .../rapidwright/ipi/PackagePinConstraint.java | 70 -- src/com/xilinx/rapidwright/ipi/XDCParser.java | 118 -- test/RapidWrightDCP | 2 +- .../rapidwright/design/TestNetTools.java | 14 +- .../design/xdc/NicerStringifyList.java | 184 +++ .../design/xdc/TestConstraintTools.java | 52 +- .../rapidwright/design/xdc/TestXDCParser.java | 1071 +++++++++++++++++ 46 files changed, 5209 insertions(+), 241 deletions(-) create mode 100644 src/com/xilinx/rapidwright/design/blocks/PblockProperty.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/ClockConstraint.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/Constraint.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/PBlockConstraint.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/PackagePinConstraint.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/UnsupportedConstraintElement.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/XDCConstraints.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/XDCParser.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/AddCellsToPblockCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/CellObject.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/CreateClockCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/CreatePBlockCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/DebugDumpCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/DesignObject.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/DumpObjsCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/EdifCellLookup.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/GetCellsCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/NameDesignObject.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/ObjType.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/ObjectGetterCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/RegularEdifCellLookup.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/ResizePBlockCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/SetPropertyCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/TclHashIdentifiedObject.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedCmdResult.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedGetterCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedIfCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedSetterCommand.java create mode 100644 src/com/xilinx/rapidwright/design/xdc/parser/XDCTools.java delete mode 100644 src/com/xilinx/rapidwright/ipi/PackagePinConstraint.java delete mode 100644 src/com/xilinx/rapidwright/ipi/XDCParser.java create mode 100644 test/src/com/xilinx/rapidwright/design/xdc/NicerStringifyList.java create mode 100644 test/src/com/xilinx/rapidwright/design/xdc/TestXDCParser.java diff --git a/.classpath b/.classpath index 8a2cfcd9c..90ab0ce0c 100644 --- a/.classpath +++ b/.classpath @@ -33,12 +33,13 @@ - + - + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c9d544eae..06f25b588 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,8 @@ on: pull_request: env: - RAPIDWRIGHT_VERSION: v2025.2.0-rc1-beta + RAPIDWRIGHT_VERSION: v2025.2.0-rc3-beta + jobs: build: diff --git a/LICENSE.TXT b/LICENSE.TXT index fd47464c5..738e6771a 100755 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -82,6 +82,7 @@ RapidWright (Apache 2.0) - junit-team-junit 4.12 (Eclipse Public License 1.0) - json (2016-08-10) (unique license) - jeromq 0.5.2 (Mozilla Public License Version 2.0) + - jacl 1.4.1 (multiple licenses) - jgrapht-core 1.3.0 (LGPL 2.1 or EPL 2.1) - Source files available: http://www.rapidwright.io/docs/_static/jgrapht-core-1.3.0-src.zip - Cap'n Proto Java Runtime 0.1.13 (MIT License) @@ -9073,3 +9074,214 @@ Public License instead of this License. ********************************* END LICENSE ********************************* + +********************** Jacl 1.4.1 (multiple licenses) **************** + +license.terms + +SUN MICROSYSTEMS, INC. THROUGH ITS SUN MICROSYSTEMS LABORATORIES +DIVISION ("SUN") WILL LICENSE THIS SOFTWARE AND THE ACCOMPANYING +DOCUMENTATION TO YOU (a "Licensee") ONLY ON YOUR ACCEPTANCE OF ALL +THE TERMS SET FORTH BELOW. + +Sun grants Licensee a non-exclusive, royalty-free right to download, +install, compile, use, copy and distribute the Software, modify or +otherwise create derivative works from the Software (each, a +"Modification") and distribute any Modification in source code and/or +binary code form to its customers with a license agreement containing +these terms and noting that the Software has been modified. The +Software is copyrighted by Sun and other third parties and Licensee +shall retain and reproduce all copyright and other notices presently +on the Software. As between Sun and Licensee, Sun is the sole owner of +all rights in and to the Software other than the limited rights +granted to Licensee herein; Licensee will own its Modifications, +expressly subject to Sun's continuing ownership of the +Software. Licensee will, at its expense, defend and indemnify Sun and +its licensors from and against any third party claims, including costs +and reasonable attorneys' fees, and be wholly responsible for any +liabilities arising out of or related to Licensee's development, use +or distribution of the Software or Modifications. Any distribution of +the Software and Modifications must comply with all applicable United +States export control laws. + +THE SOFTWARE IS BEING PROVIDED TO LICENSEE "AS IS" AND ALL EXPRESS OR +IMPLIED CONDITIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, +ARE DISCLAIMED. IN NO EVENT WILL SUN BE LIABLE HEREUNDER FOR ANY +DIRECT DAMAGES OR ANY INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES OF ANY KIND. + +license.amd + +The following license terms apply to the TJC compiler source +and test files located in the src/tjc, src/tests/tjc, and +tests/tjc directories. + +© 2005 Advanced Micro Devices, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that existing copyright notices +are retained in all copies, this notice is included verbatim in any +distributions, and the terms and conditions hererin are met. + +Use of the this software manifests acceptance of the terms of this +license by performance. + +The name of Advanced Micro Devices, Inc. may not be used to endorse or +promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY ADVANCED MICRO DEVICES, INC. "AS IS" AND ANY +EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NON-INFRINGEMENT, OR THOSE ARISING FROM CUSTOM OF TRADE OR +COURSE OF USAGE ARE DISCLAIMED. + +IN NO EVENT SHALL ADVANCED MICRO DEVICES, INC. BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 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 OF THIS SOFTWARE ITS DOCUMENTATION OR ANY DERIVATIVES +THEREOF, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. BY USING THIS +SOFTWARE WITHOUT CHARGE, YOU ACCEPT THIS ALLOCATION OF RISK. THIS +DISCLAIMER OF LIABILITY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. +ADVANCED MICRO DEVICES, INC. HAS NO OBLIGATION TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS OF THIS SOFTWARE. + +In the redistribution and use of this software, each party shall at all +times comply with all applicable governmental laws, statutes, ordinances, +rules, regulations, orders, and other requirements, including without +limitation such governmental requirements applicable to environmental +protection, health, safety, wages, hours, equal employment opportunity, +nondiscrimination, working conditions, import or export control, and +transportation. Without limiting the foregoing, each party shall adhere +to the U.S. Export Administration Regulations (EAR), currently found at +15 C.F.R. Sections 730 through 744, and, unless properly authorized by +the U.S. Government, shall not (1) export, re-export or release restricted +technology, software, or source code to a national of a country in Country +Groups D:1 or E:1, or (2) export to Country Groups D:1 or E:1 the direct +product of such technology or software, if such foreign produced direct +product is subject to national security controls as identified on the +Commerce Control List (currently found in Supplement 1 to Section 774 of EAR). +These export requirements shall survive any expiration or termination +of this agreement. + +license.itcl + +The following license terms apply to the Itcl source and +test files located in the src/itcl and tests/itcl directories. + + +This software is copyrighted by Cadence Design Systems, Inc., and other +parties. The following terms apply to all files associated with the +software unless explicitly disclaimed in individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. + +----------------------------------------------------------------------- + Following is the original agreement for the Tcl/Tk software from + Sun Microsystems. +----------------------------------------------------------------------- + +This software is copyrighted by the Regents of the University of +California, Sun Microsystems, Inc., and other parties. The following +terms apply to all files associated with the software unless explicitly +disclaimed in individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. + +license.ucb + +Portions of Jacl and Tcl Blend are +Copyright (c) 1997-1999 The Regents of the University of California. +All rights reserved. + +Permission is hereby granted, without written agreement and without +license or royalty fees, to use, copy, modify, and distribute this +software and its documentation for any purpose, provided that the above +copyright notice and the following two paragraphs appear in all copies +of this software. + +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF +THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE +PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF +CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +ENHANCEMENTS, OR MODIFICATIONS. + + +********************************* END LICENSE ********************************* \ No newline at end of file diff --git a/common.gradle b/common.gradle index 7d65de368..3531f40c3 100644 --- a/common.gradle +++ b/common.gradle @@ -48,6 +48,7 @@ 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 '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' testFixturesApi 'org.junit.jupiter:junit-jupiter-params:5.7.1' diff --git a/src/com/xilinx/rapidwright/design/NetTools.java b/src/com/xilinx/rapidwright/design/NetTools.java index f2eeeac47..c25e1ee00 100644 --- a/src/com/xilinx/rapidwright/design/NetTools.java +++ b/src/com/xilinx/rapidwright/design/NetTools.java @@ -45,6 +45,7 @@ public class NetTools { public static final String WHITE_SPACE = " "; private static Set clkSrcSiteTypeEnums = EnumSet.noneOf(SiteTypeEnum.class); + static { clkSrcSiteTypeEnums.add(SiteTypeEnum.BUFGCE); // All supported series clkSrcSiteTypeEnums.add(SiteTypeEnum.BUFGCTRL); // All supported series @@ -59,17 +60,19 @@ public class NetTools { public static boolean isGlobalClock(Net net) { SitePinInst srcSpi = net.getSource(); if (srcSpi == null) - return false; - + return false; + return clkSrcSiteTypeEnums.contains(srcSpi.getSiteTypeEnum()); } public static class NodeTree extends Node { private static final long serialVersionUID = 8522818939039826954L; public List fanouts = Collections.emptyList(); + public NodeTree(Node node) { super(node); } + public boolean multiplyDriven = false; public void addFanout(NodeTree node) { @@ -136,6 +139,7 @@ public String toString() { /** * Returns a string representation of this NodeTree using tree characters (├─, └─). + * * @return String representation with tree characters. */ public String toTreeString() { @@ -146,9 +150,9 @@ public String toTreeString() { /** * Returns a string representation of this NodeTree using tree characters (├─, └─). - * - * @param customToString Allows a custom toString() method to be applied to the Node when - * generating the String. + * + * @param customToString Allows a custom toString() method to be applied to the Node when + * generating the String. * @return String representation with tree characters. */ public String toTreeString(Function customToString) { @@ -157,8 +161,8 @@ public String toTreeString(Function customToString) { return sb.toString(); } - private void buildTreeString(StringBuilder sb, String prefix, String childPrefix, - Set visited, Function customToString) { + private void buildTreeString(StringBuilder sb, String prefix, String childPrefix, + Set visited, Function customToString) { sb.append(prefix); sb.append(customToString.apply(this)); if (multiplyDriven && !visited.add(this)) { @@ -181,19 +185,21 @@ private void buildTreeString(StringBuilder sb, String prefix, String childPrefix * Note that this method only discovers subtrees that start at an output SitePinInst or a node tied to VCC/GND * (i.e. gaps and islands will be ignored). * Nodes that are multiply-driven (indicative of routing loops) will have their NodeTree.multiplyDriven flag set. + * * @param net Net to analyze. * @return A list of NodeTree objects, corresponding to the root of each subtree. */ public static List getNodeTrees(Net net) { return getNodeTrees(net, n -> false); } - + /** * Compute the node routing tree of the given Net by examining its PIPs. * Note that this method only discovers subtrees that start at an output SitePinInst or a node tied to VCC/GND * (i.e. gaps and islands will be ignored). * Nodes that are multiply-driven (indicative of routing loops) will have their NodeTree.multiplyDriven flag set. - * @param net Net to analyze. + * + * @param net Net to analyze. * @param filter A function that when is applied to a node, if it returns true will be excluded from the tree. * @return A list of NodeTree objects, corresponding to the root of each subtree. */ @@ -209,10 +215,10 @@ public static List getNodeTrees(Net net, Function filte if (filter.apply(start)) continue; Node end = pip.getEndNode(); if (filter.apply(end)) continue; - + boolean isReversed = pip.isReversed(); NodeTree startNode = nodeMap.computeIfAbsent(isReversed ? end : start, NodeTree::new); - NodeTree endNode = nodeMap.compute(isReversed ? start : end, (k,v) -> { + NodeTree endNode = nodeMap.compute(isReversed ? start : end, (k, v) -> { if (v == null) { v = new NodeTree(k); } else { @@ -224,7 +230,7 @@ public static List getNodeTrees(Net net, Function filte startNode.addFanout(endNode); if (!pip.isBidirectional()) { if ((net.getType() == NetType.GND && startNode.isTiedToGnd()) || - (net.getType() == NetType.VCC && startNode.isTiedToVcc())) { + (net.getType() == NetType.VCC && startNode.isTiedToVcc())) { subtrees.add(startNode); } } @@ -241,15 +247,15 @@ public static List getNodeTrees(Net net, Function filte return subtrees; } - + /** * Checks if the provided net drives a clock site pin input. - * + * * @param net The net to examine. * @return True if the net has a site pin clock input, false otherwise. */ public static boolean hasClockSinks(Net net) { - for (SitePinInst sink : net.getPins()) { + for (SitePinInst sink : net.getPins()) { if (sink.isOutPin()) continue; if (sink.getName().contains("CLK")) return true; } @@ -259,18 +265,18 @@ public static boolean hasClockSinks(Net net) { /** * Returns a string representation of the net's routing tree using tree * characters (├─, └─). - * + * * @param net The net to generate the tree of nodes from. * @return String representation of the net's routing tree with tree characters. */ - public static String getNetTreeString(Net net) { + public static String getNetTreeString(Net net) { return getNetTreeString(net, n -> false); } /** * Returns a string representation of the net's routing tree using tree * characters (├─, └─). - * + * * @param net The net to generate the tree of nodes from. * @param filter A function that when returns true for a node, the node should * be excluded. @@ -283,7 +289,7 @@ public static String getNetTreeString(Net net, Function filter) { /** * Returns a string representation of the net's routing tree using tree * characters (├─, └─). - * + * * @param net The net to generate the tree of nodes from. * @param filter A function that when returns true for a node, the node * should be excluded. @@ -292,7 +298,7 @@ public static String getNetTreeString(Net net, Function filter) { * @return String representation of the net's routing tree with tree characters. */ public static String getNetTreeString(Net net, Function filter, - Function customToString) { + Function customToString) { List subtrees = getNodeTrees(net, filter); if (subtrees.isEmpty()) { return ""; @@ -310,17 +316,159 @@ public static String getNetTreeString(Net net, Function filter, /** * Generates the clock tree up to the horizontal distribution nodes of the * provided clock net up to the NODE_PINFEED nodes. - * + * * @param net The clock net * @return A string tree representation from the source of the clock out to all - * horizontal distribution nodes. + * horizontal distribution nodes. */ public static String getClockTreeSpine(Net net) { - Function customToString = - n -> n.getTileName() + "/" + n.getWireName() - + " (" + n.getIntentCode() + ") CR=" + Function customToString = + n -> n.getTileName() + "/" + n.getWireName() + + " (" + n.getIntentCode() + ") CR=" + n.getTile().getClockRegion(); Function excludeFilter = n -> n.getIntentCode() == IntentCode.NODE_PINFEED; return getNetTreeString(net, excludeFilter, customToString); } + + /** + * Gets a string representation of the vertical clock routing spine for the given net. + * This method is similar to getClockTreeSpine but only includes vertical distribution nodes. + * Only the clock root VROUTE node is included; all other VROUTE nodes are filtered out. + * + * @param net The net to analyze + * @return A string tree representation showing only vertical clock routing nodes + */ + public static String getVerticalClockTreeSpine(Net net) { + Function customToString = + n -> n.getIntentCode() + " CR=" + n.getTile().getClockRegion(); + + // Build the full tree without filtering (except PINFEED) + Function minimalFilter = n -> n.getIntentCode() == IntentCode.NODE_PINFEED; + List subtrees = getNodeTrees(net, minimalFilter); + + if (subtrees.isEmpty()) { + return ""; + } + + // Find the clock root VROUTE node + Node clockRoot = findClockRootVRoute(net); + + // Define the set of nodes we want to display + Set displayIntentCodes = EnumSet.of( + IntentCode.NODE_GLOBAL_VDISTR_SHARED, + IntentCode.NODE_GLOBAL_VROUTE, + IntentCode.NODE_GLOBAL_VDISTR_LVL1, + IntentCode.NODE_GLOBAL_VDISTR, + IntentCode.NODE_GLOBAL_VDISTR_LVL2, + IntentCode.NODE_GLOBAL_VDISTR_LVL21, + IntentCode.NODE_GLOBAL_VDISTR_LVL3 + ); + + if (subtrees.size() > 1) { + throw new RuntimeException("Clock route might have multiple clock roots"); + } + + Function includeFilter = n -> { + IntentCode intentCode = n.getIntentCode(); + if (displayIntentCodes.contains(intentCode)) { + if (intentCode == IntentCode.NODE_GLOBAL_VROUTE) { + return n.equals(clockRoot); + } + return true; + } + return false; + }; + + StringBuilder sb = new StringBuilder(); + NodeTree subtree = subtrees.get(0); + NodeTree filteredSubtree = buildFilteredTree(subtree, includeFilter); + sb.append(filteredSubtree.toTreeString(customToString)); + return sb.toString(); + } + + /** + * Builds a tree that only includes the nodes that pass the filter while maintaining connectivity. + */ + private static NodeTree buildFilteredTree(NodeTree nodeTree, Function includeFilter) { + class WorkItem { + final NodeTree original; + final NodeTree parentInFilteredTree; + + WorkItem(NodeTree original, NodeTree parentInFilteredTree) { + this.original = original; + this.parentInFilteredTree = parentInFilteredTree; + } + } + + NodeTree filteredRoot = nodeTree; + + while (!includeFilter.apply(filteredRoot)) { + if (filteredRoot.fanouts.size() != 1) { + throw new RuntimeException("Failed to find clock root"); + } + filteredRoot = filteredRoot.fanouts.get(0); + } + + List worklist = new ArrayList<>(); + NodeTree filteredNodeTree = new NodeTree(filteredRoot); + + for (NodeTree fanout : filteredRoot.fanouts) { + worklist.add(new WorkItem(fanout, filteredNodeTree)); + } + + // Process worklist for the normal case (root passed filter) + while (!worklist.isEmpty()) { + WorkItem item = worklist.remove(0); + NodeTree curr = item.original; + NodeTree filteredParent = item.parentInFilteredTree; + + if (includeFilter.apply(curr)) { + NodeTree filteredNode = new NodeTree(curr); + filteredNode.multiplyDriven = curr.multiplyDriven; + filteredParent.addFanout(filteredNode); + + for (NodeTree fanout : curr.fanouts) { + worklist.add(new WorkItem(fanout, filteredNode)); + } + } else { + for (NodeTree fanout : curr.fanouts) { + worklist.add(new WorkItem(fanout, filteredParent)); + } + } + } + + return filteredNodeTree; + } + + /** + * Finds the clock root VROUTE node. + * The clock root is the deepest (furthest from source) NODE_GLOBAL_VROUTE node in the tree. + * + * @param net The net to analyze + * @return The clock root VROUTE node, or null if none found + */ + public static Node findClockRootVRoute(Net net) { + List subtrees = getNodeTrees(net); + + if (subtrees.size() > 1) { + throw new RuntimeException("Clock route potentially has multiple clock roots"); + } + + // Find the last GLOBAL_VROUTE node before transitioning VDISTR nodes + NodeTree curr = subtrees.get(0); + NodeTree deepestVRoute = null; + + while (curr.fanouts.size() == 1) { + if (curr.getIntentCode() == IntentCode.NODE_GLOBAL_VROUTE) { + deepestVRoute = curr; + } + curr = curr.fanouts.get(0); + } + + if (curr.getIntentCode() == IntentCode.NODE_GLOBAL_VROUTE) { + deepestVRoute = curr; + } + + return deepestVRoute; + } } diff --git a/src/com/xilinx/rapidwright/design/blocks/PBlock.java b/src/com/xilinx/rapidwright/design/blocks/PBlock.java index 0bf236dc9..1e6b00561 100644 --- a/src/com/xilinx/rapidwright/design/blocks/PBlock.java +++ b/src/com/xilinx/rapidwright/design/blocks/PBlock.java @@ -58,6 +58,11 @@ public class PBlock extends ArrayList { private PBlock parent; private boolean containRouting; + + private boolean isSoft; + + private boolean excludePlacement; + /** Set of all basic sites that can be referenced in a PBlock */ private static HashSet pblockTypes; @@ -191,12 +196,17 @@ public ArrayList getTclConstraints() { ArrayList tcl = new ArrayList<>(); tcl.add("create_pblock " + name + (parent != null ? " -parent " + parent.getName() : "")); for (PBlockRange p : this) { - tcl.add("resize_pblock "+ name +" -add " + p.toString()); + tcl.add("resize_pblock [get_pblocks "+ name +"] -add " + p.toString()); } if (containRouting()) { - tcl.add("set_property CONTAIN_ROUTING 1 [get_pblocks "+name+"]"); + tcl.add("set_property " + PblockProperty.CONTAIN_ROUTING + " 1 [get_pblocks " + name + "]"); + } + if (isSoft()) { + tcl.add("set_property " + PblockProperty.IS_SOFT + " 1 [get_pblocks " + name + "]"); + } + if (excludePlacement()) { + tcl.add("set_property " + PblockProperty.EXCLUDE_PLACEMENT + " 1 [get_pblocks " + name + "]"); } - return tcl; } @@ -565,6 +575,22 @@ public void setContainRouting(boolean containRouting) { this.containRouting = containRouting; } + public boolean isSoft() { + return isSoft; + } + + public void setIsSoft(boolean isSoft) { + this.isSoft = isSoft; + } + + public boolean excludePlacement() { + return excludePlacement; + } + + public void setExcludePlacement(boolean excludePlacement) { + this.excludePlacement = excludePlacement; + } + /** * Returns true if the provided site type is referenced in a pblock corner. * @param type The site type in question. diff --git a/src/com/xilinx/rapidwright/design/blocks/PBlockRange.java b/src/com/xilinx/rapidwright/design/blocks/PBlockRange.java index d4a2750e7..5a429dd0c 100644 --- a/src/com/xilinx/rapidwright/design/blocks/PBlockRange.java +++ b/src/com/xilinx/rapidwright/design/blocks/PBlockRange.java @@ -105,7 +105,16 @@ public void setUpperRight(PBlockCorner upperRight) { this.upperRight = upperRight; } + private static String getPrefixedCornerName(PBlockCorner corner) { + if (corner instanceof ClockRegion) { + return CLOCK_REGION_RANGE_STR+"_"+corner.getName(); + } + return corner.getName(); + } public String toString() { + if (isClockRegionRange()) { + return CLOCK_REGION_RANGE_STR + "_" + lowerLeft.getName() + ":" + CLOCK_REGION_RANGE_STR + "_" + upperRight.getName(); + } return lowerLeft.getName() + ":" + upperRight.getName(); } diff --git a/src/com/xilinx/rapidwright/design/blocks/PblockProperty.java b/src/com/xilinx/rapidwright/design/blocks/PblockProperty.java new file mode 100644 index 000000000..fad64cf6f --- /dev/null +++ b/src/com/xilinx/rapidwright/design/blocks/PblockProperty.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Coherent Ho, Synopsys, Inc. + * + * 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.design.blocks; + + +/** + * Enum representing different PBlock properties that may be set in XDC. + * Created on: Nov 7, 2025 + */ +public enum PblockProperty { + CONTAIN_ROUTING, + IS_SOFT, + EXCLUDE_PLACEMENT; +} diff --git a/src/com/xilinx/rapidwright/design/xdc/ClockConstraint.java b/src/com/xilinx/rapidwright/design/xdc/ClockConstraint.java new file mode 100644 index 000000000..178517db0 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/ClockConstraint.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc; + +import java.util.Locale; + +/** + * A constraint representing a clock + */ +public class ClockConstraint implements Constraint{ + private String clockName; + private double period; + + private String portName; + + public ClockConstraint(String clockName, double period, String portName) { + this.clockName = clockName; + this.period = period; + this.portName = portName; + } + + @Override + public ClockConstraint clone() { + return new ClockConstraint(clockName, period, portName); + } + + public String getClockName() { + return clockName; + } + + public void setClockName(String clockName) { + this.clockName = clockName; + } + + public double getPeriod() { + return period; + } + + public void setPeriod(double period) { + this.period = period; + } + + public String asXdc() { + String periodString = String.format(Locale.US, "%.3f", period); + return "create_clock -period "+periodString+" -name "+clockName+" [get_ports "+portName+"]"; + } + + public String getPortName() { + return portName; + } + + public void setPortName(String portName) { + this.portName = portName; + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/Constraint.java b/src/com/xilinx/rapidwright/design/xdc/Constraint.java new file mode 100644 index 000000000..5831ea303 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/Constraint.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc; +public interface Constraint> { + T clone(); +} diff --git a/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java b/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java index 45078648d..9d121fafa 100644 --- a/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java +++ b/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java @@ -28,6 +28,7 @@ import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.blocks.PBlock; import com.xilinx.rapidwright.design.ConstraintGroup; +import com.xilinx.rapidwright.design.xdc.parser.RegularEdifCellLookup; /** * A collection of methods to access design constraints. @@ -35,20 +36,17 @@ * Created on: Oct 31, 2025 */ public class ConstraintTools { + public static Map getPBlockFromXDCConstraints(Design d) { Map pblockMap = new HashMap<>(); + for (ConstraintGroup cg : ConstraintGroup.values()) { - for (String tclLine : d.getXDCConstraints(cg)) { - if (tclLine.contains("resize_pblock")) { - //resize_pblock [get_pblocks pblock_base_mb_i] -add {CLOCKREGION_X1Y1:CLOCKREGION_X1Y1} - //resize_pblock [get_pblocks pblock_dbg_hub] -add {CLOCKREGION_X0Y1:CLOCKREGION_X0Y1} - //resize_pblock [get_pblocks pblock_u_ila_0] -add {CLOCKREGION_X2Y1:CLOCKREGION_X2Y1} - String name = tclLine.substring(tclLine.indexOf("[get_pblocks ") + 13, tclLine.indexOf("]")); - String range = tclLine.substring(tclLine.indexOf("{") + 1, tclLine.indexOf("}")); - pblockMap.put(name, new PBlock(d.getDevice(), range)); - } - } + XDCConstraints xdcConstraints = XDCParser.parseXDC(d.getDevice(), d.getXDCConstraints(cg), new RegularEdifCellLookup(d.getNetlist())); + xdcConstraints.getPBlockConstraints().forEach((k,v)->{ + pblockMap.put(k, v.getPblock()); + }); } + return pblockMap; } } diff --git a/src/com/xilinx/rapidwright/design/xdc/PBlockConstraint.java b/src/com/xilinx/rapidwright/design/xdc/PBlockConstraint.java new file mode 100644 index 000000000..9b563d468 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/PBlockConstraint.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.blocks.PBlock; + +public class PBlockConstraint implements Constraint { + private PBlock pblock = new PBlock(); + private List cells = new ArrayList<>(); + + @Override + public PBlockConstraint clone() { + PBlockConstraint res = new PBlockConstraint(); + res.pblock = pblock; + res.cells = new ArrayList<>(cells); + return res; + } + + public PBlock getPblock() { + return pblock; + } + + public List getCells() { + return cells; + } + + public Stream asXdc() { + List res = getPblock().getTclConstraints(); + if (cells.size()==1 && cells.get(0).isEmpty()) { + res.add("add_cells_to_pblock [get_pblocks "+pblock.getName()+"] -top"); + } else if (!cells.isEmpty()) { + res.add("add_cells_to_pblock [get_pblocks "+pblock.getName()+"] [get_cells {"+String.join(" ", cells)+"}]"); + } + return res.stream(); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/PackagePinConstraint.java b/src/com/xilinx/rapidwright/design/xdc/PackagePinConstraint.java new file mode 100644 index 000000000..e96066e36 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/PackagePinConstraint.java @@ -0,0 +1,108 @@ +/* + * + * Copyright (c) 2018-2022, Xilinx, Inc. + * Copyright (c) 2022, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Chris Lavin, Xilinx Research Labs. + * + * 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.design.xdc; + +import java.util.stream.Stream; + +/** + * Annotates a package pin name with an IO standard + * Created on: Jan 25, 2018 + */ +public class PackagePinConstraint implements Constraint { + + private String portName; + + private String packagePin; + + private String ioStandard; + + public PackagePinConstraint() { + } + + @Override + public PackagePinConstraint clone() { + PackagePinConstraint res = new PackagePinConstraint(portName); + res.setPackagePin(packagePin); + res.setIOStandard(ioStandard); + return res; + } + + public PackagePinConstraint(String portName) { + setPortName(portName); + } + + /** + * @return the name + */ + public String getPackagePin() { + return packagePin; + } + + /** + * @param packagePin the name to set + */ + public void setPackagePin(String packagePin) { + this.packagePin = packagePin; + } + + /** + * @return the ioStandard + */ + public String getIoStandard() { + return ioStandard; + } + + /** + * @param ioStandard the ioStandard to set + */ + public void setIOStandard(String ioStandard) { + this.ioStandard = ioStandard; + } + + public String toString() { + return packagePin + ":" + ioStandard; + } + + public String getPortName() { + return portName; + } + + public Stream asXdc() { + Stream.Builder res = Stream.builder(); + if (packagePin!=null) { + res.add("set_property PACKAGE_PIN "+packagePin+" [get_ports "+portName+"]"); + } + if (ioStandard!=null) { + res.add("set_property IOSTANDARD "+ioStandard+" [get_ports "+portName+"]"); + } + return res.build(); + } + + public void setPortName(String portName) { + this.portName = portName; + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/UnsupportedConstraintElement.java b/src/com/xilinx/rapidwright/design/xdc/UnsupportedConstraintElement.java new file mode 100644 index 000000000..5e7aa1b2b --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/UnsupportedConstraintElement.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.parser.CellObject; +import com.xilinx.rapidwright.design.xdc.parser.DesignObject; +import com.xilinx.rapidwright.design.xdc.parser.EdifCellLookup; +import com.xilinx.rapidwright.design.xdc.parser.TclHashIdentifiedObject; +import com.xilinx.rapidwright.design.xdc.parser.XDCTools; +import org.apache.commons.io.FilenameUtils; +import org.jetbrains.annotations.NotNull; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclObject; +import tcl.lang.TclToken; + +/** + * Stringified representation of a (partial) constraint that our parser does not currently support. + * + * Subclasses specify what type of data a class instance contains + */ +public abstract class UnsupportedConstraintElement { + public static String toXdc(Stream unsupportedConstraintElements) { + return unsupportedConstraintElements.map(e->e.toXdc()).collect(Collectors.joining()); + } + + public static Stream wrapStream(Stream inner, Stream prefix, Stream suffix) { + if (prefix !=null) { + inner = Stream.concat( + prefix, + inner + ); + } + if (suffix != null) { + inner = Stream.concat( + inner, + suffix + ); + } + return inner; + + } + public static Stream wrapStream(Stream inner, String prefix, String suffix) { + if (prefix !=null) { + inner = Stream.concat( + Stream.of(new SyntaxConstraintElement(prefix)), + inner + ); + } + if (suffix != null) { + inner = Stream.concat( + inner, + Stream.of(new SyntaxConstraintElement(suffix)) + ); + } + return inner; + } + + + public static Function> addSpacesBetween(Function> innerFunc) { + final boolean[] first = {true}; + return e->{ + if (first[0]) { + first[0] =false; + return innerFunc.apply(e); + } + return Stream.concat(Stream.of(new SyntaxConstraintElement(" ")), innerFunc.apply(e)); + }; + } + + public static Function> addSpacesBetween() { + final boolean[] first = {true}; + return e->{ + if (first[0]) { + first[0] =false; + return Stream.of(e); + } + return Stream.of(new SyntaxConstraintElement(" "), e); + }; + } + + public static @NotNull List commandToUnsupportedConstraints(Interp interp, TclObject[] objv, EdifCellLookup cellLookup) { + return Arrays.stream(objv) + .flatMap(addSpacesBetween(obj -> objToUnsupportedConstraintElement(interp, obj, cellLookup, false, false))) + .collect(Collectors.toList()); + } + + public abstract String toXdc(); + + public abstract boolean referencesCell(String name); + + + /** + * Unsupported Constraint Element that contains any text or command name except cells + */ + public static class NameConstraintElement extends UnsupportedConstraintElement { + private final String text; + + public NameConstraintElement(String text) { + this.text = text; + } + + @Override + public String toXdc() { + return text; + } + + @Override + public boolean referencesCell(String name) { + return false; + } + + @Override + public String toString() { + return "N<"+toXdc()+">"; + } + } + + /** + * Unsupported Constraint Element that contains Tcl syntax + */ + public static class SyntaxConstraintElement extends UnsupportedConstraintElement { + private final String text; + + public SyntaxConstraintElement(String text) { + this.text = text; + } + + @Override + public String toXdc() { + return text; + } + + @Override + public boolean referencesCell(String name) { + return false; + } + + @Override + public String toString() { + return "S<"+toXdc()+">"; + } + } + + /** + * Unsupported Constraint Element that references a cell name + */ + public static class CellConstraintElement extends UnsupportedConstraintElement{ + private final String cellName; + + public CellConstraintElement(String cellName) { + this.cellName = cellName; + } + + @Override + public String toXdc() { + return cellName; + } + + @Override + public boolean referencesCell(String name) { + return cellName.equals(name); + } + + public String getCellName() { + return cellName; + } + + @Override + public String toString() { + return "C<"+toXdc()+">"; + } + } + + + /** + * Convert a TclObject into UnsupportedConstraintElements + */ + public static Stream objToUnsupportedConstraintElement(Interp interp, TclObject obj, EdifCellLookup lookup, boolean replaceProbableCells, boolean applyWildcardsChooseAny) { + try { + Optional> designObject = DesignObject.unwrapTclObject(interp, obj, lookup); + if (designObject.isPresent()) { + return designObject.get().toUnsupportedConstraintElement(); + } + if (obj.getInternalRep() instanceof TclList) { + TclObject[] elements = TclList.getElements(interp, obj); + + Stream inner = Arrays.stream(elements).flatMap(e -> objToUnsupportedConstraintElement(interp, e, lookup, false, applyWildcardsChooseAny)); + return wrapStream(inner, "{","}"); + } + + final boolean[] startsWithCell = {false}; + final T[] cell = (T[]) new Object[]{null}; + + String s = obj.toString(); + List res = new ArrayList<>(); + TclHashIdentifiedObject.unpack(interp, s, partS -> { + res.add(new UnsupportedConstraintElement.NameConstraintElement(partS)); + }, partObj -> { + DesignObject po = DesignObject.requireCastUnwrappedObject(partObj, lookup); + if (po instanceof CellObject && cell[0]==null) { + List cells = ((CellObject) po).getCells(); + if (cells.size()!=1) { + throw new RuntimeException("should have one cell??"); + } + startsWithCell[0] = true; + + T c = cells.iterator().next(); + cell[0] = c; + } else { + po.toUnsupportedConstraintElement().forEach(res::add); + } + }); + + if (replaceProbableCells) { + absorbIntoCells(lookup, res, (T) cell[0], applyWildcardsChooseAny); + } else if (cell[0]!=null) { + res.add(0, new UnsupportedConstraintElement.CellConstraintElement(lookup.getAbsoluteFinalName(cell[0]))); + } + + if (XDCTools.stringNeedsBraces(s)) { + return wrapStream(res.stream(), "{", "}"); + } + return res.stream(); + + } catch (TclException e) { + throw new RuntimeException(e); + } + } + + /** + * If a cell name is followed by text that looks like a subcell reference, absorb that text into the cell name + */ + private static void absorbIntoCells(EdifCellLookup lookup, List res, T cell, boolean applyWildcardsChooseAny) { + if (res.size()!=1 || lookup==null) { + return; + } + String suffix = res.get(0).toXdc(); + if (cell != null) { + if (!suffix.startsWith("/")) { + //Suffix should always start with slash, something unsupported is happening + + //Currently unimplemented! The code is trying to access a sibling cell on the same hierarchy level. + //Need to strip last level off cell, prepend suffix with its name + throw new RuntimeException("Suffix should start with slash!"); + } + suffix = suffix.substring(1); + } else { + cell = lookup.getRoot(); + } + + while (suffix!=null) { + System.out.println("try to expand " + cell + " and " + suffix); + int slashPos = suffix.indexOf("/"); + String currLevel, remaining; + if (slashPos == -1) { + currLevel = suffix; + remaining = null; + } else { + currLevel = suffix.substring(0, slashPos); + remaining = suffix.substring(slashPos+1); + } + System.out.println("decomposed into "+currLevel+" and "+remaining); + T child = lookup.getChild(cell, currLevel); + if (child == null) { + if (applyWildcardsChooseAny) { + List matches = lookup.getChildrenOf(cell) + .filter(x-> FilenameUtils.wildcardMatch(lookup.getRelativeOriginalName(x), currLevel)) + .collect(Collectors.toList()); + if (matches.isEmpty()) { + break; + } + child = matches.get(0); + if (matches.size() > 1) { + System.out.println("chose arbitrary wildcard match for "+currLevel+": "+child); + } + } else { + break; + } + } + if (child == null) { + break; + } + cell = child; + suffix = remaining; + } + + if (cell==lookup.getRoot()) { + return; + } + System.out.println("applying absorption: "+cell+" and "+suffix); + + res.clear(); + res.add(new UnsupportedConstraintElement.CellConstraintElement(lookup.getAbsoluteFinalName(cell))); + if (suffix!=null) { + res.add(new NameConstraintElement("/"+suffix)); + } + + System.out.println(res); + + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/XDCConstraints.java b/src/com/xilinx/rapidwright/design/xdc/XDCConstraints.java new file mode 100644 index 000000000..e5833037e --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/XDCConstraints.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.parser.XDCTools; +import com.xilinx.rapidwright.util.Pair; + +public class XDCConstraints { + private Map pinConstraints = new HashMap<>(); + private Map clockConstraints = new HashMap<>(); + private Map> cellProperties = new HashMap<>(); + private List> unsupportedConstraints = new ArrayList<>(); + private Map pBlockConstraints = new HashMap<>(); + + + + public XDCConstraints(Map pinConstraints, + Map clockConstraints, + Map> cellProperties, + List> unsupportedConstraints) { + this.pinConstraints = pinConstraints; + this.clockConstraints = clockConstraints; + this.cellProperties = cellProperties; + this.unsupportedConstraints = unsupportedConstraints; + } + + private static > Map cloneMap(Map map) { + return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e->e.getValue().clone())); + } + private static Map> cloneStringMap(Map> map) { + return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e->new HashMap<>(e.getValue()))); + } + + private static List> cloneList(List> list) { + return list.stream().map(ArrayList::new).collect(Collectors.toList()); + } + public XDCConstraints clone() { + return new XDCConstraints( + cloneMap(pinConstraints), + cloneMap(clockConstraints), + cloneStringMap(cellProperties), + cloneList(unsupportedConstraints) + ); + } + + public XDCConstraints() { + } + + public Map getPinConstraints() { + return pinConstraints; + } + + public Map getClockConstraints() { + return clockConstraints; + } + + public Map> getCellProperties() { + return cellProperties; + } + + public List> getUnsupportedConstraints() { + return unsupportedConstraints; + } + + public Map getPBlockConstraints() { + return pBlockConstraints; + } + + private static Stream cellPropsToXdc(int counter, String cell, Map properties) { + if (properties.size()<2) { + return properties.entrySet().stream().map(propToValue-> + "set_property " + propToValue.getKey() + " " + XDCTools.braceEnclosedIfNeeded(propToValue.getValue()) + " [get_cells {" + cell + "}]" + ); + } + + String varName = "rw_getcell_"+counter; + String initVarLine = "set "+varName+ " [get_cells {" + cell + "}]"; + return Stream.concat( + Stream.of(initVarLine), + properties.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(propToValue-> + "set_property " + propToValue.getKey() + " " + XDCTools.braceEnclosedIfNeeded(propToValue.getValue()) + " $"+varName + ) + ); + } + + public Stream getAllAsXdc() { + Stream clocks = clockConstraints.values().stream().sorted(Comparator.comparing(ClockConstraint::getPortName)).map(ClockConstraint::asXdc); + Stream unsupported = unsupportedConstraints.stream().map(e->UnsupportedConstraintElement.toXdc(e.stream())); + + AtomicInteger varCounter = new AtomicInteger(); + Stream cellProps = cellProperties.entrySet().stream().sorted(Map.Entry.comparingByKey()).flatMap( + cellToProps -> cellPropsToXdc(varCounter.getAndIncrement(), cellToProps.getKey(), cellToProps.getValue())); + Stream pinConstrs = pinConstraints.values().stream().sorted(Comparator.comparing(PackagePinConstraint::getPortName)).flatMap(PackagePinConstraint::asXdc); + Stream pblockConstrs = pBlockConstraints.values().stream().sorted(Comparator.comparing(pBlockConstraint -> pBlockConstraint.getPblock().getName())).flatMap(PBlockConstraint::asXdc); + + + return Stream.of(clocks, cellProps, pinConstrs, pblockConstrs, unsupported).flatMap(e->e); + } + + public void writeToFile(Path file) { + try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(file))) { + getAllAsXdc().forEach(pw::println); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private List rewriteUnsupported(List list, UnaryOperator cellNameMapper) { + return list.stream().map(elem -> { + if (!(elem instanceof UnsupportedConstraintElement.CellConstraintElement)) { + return elem; + } + UnsupportedConstraintElement.CellConstraintElement cellElem = (UnsupportedConstraintElement.CellConstraintElement) elem; + return new UnsupportedConstraintElement.CellConstraintElement(cellNameMapper.apply(cellElem.getCellName())); + }).collect(Collectors.toList()); + } + + public XDCConstraints duplicateWithReplacedCellNames(UnaryOperator cellNameMapper) { + Map> rewrittenProperties = cellProperties.entrySet().stream() + .flatMap(e -> e.getValue().entrySet().stream().map(e2 -> new Pair<>(e.getKey(), e2))) + .collect(Collectors.groupingBy( + e -> cellNameMapper.apply(e.getFirst()), Collectors.toMap( + e->e.getSecond().getKey(), + e->e.getSecond().getValue(), + (a,b) -> { + if (!a.equals(b)) { + throw new IllegalStateException("Cannot merge values "+a+" and "+b); + } + return a; + } + ) + )); + List> rewrittenUnsupported = unsupportedConstraints.stream() + .map(l->rewriteUnsupported(l, cellNameMapper)).collect(Collectors.toList()); + return new XDCConstraints( + pinConstraints, + clockConstraints, + rewrittenProperties, + rewrittenUnsupported + ); + } + + public boolean isCellReferencedInConstraints(String name) { + return unsupportedConstraints.stream().anyMatch(elements -> elements.stream().anyMatch(elem -> elem.referencesCell(name))); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/XDCParser.java b/src/com/xilinx/rapidwright/design/xdc/XDCParser.java new file mode 100644 index 000000000..146b00dee --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/XDCParser.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2017-2022, Xilinx, Inc. + * Copyright (c) 2022, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Chris Lavin, Xilinx Research Labs. + * + * 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.design.xdc; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Objects; + +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.xdc.parser.AddCellsToPblockCommand; +import com.xilinx.rapidwright.design.xdc.parser.CreateClockCommand; +import com.xilinx.rapidwright.design.xdc.parser.CreatePBlockCommand; +import com.xilinx.rapidwright.design.xdc.parser.DebugDumpCommand; +import com.xilinx.rapidwright.design.xdc.parser.DumpObjsCommand; +import com.xilinx.rapidwright.design.xdc.parser.EdifCellLookup; +import com.xilinx.rapidwright.design.xdc.parser.GetCellsCommand; +import com.xilinx.rapidwright.design.xdc.parser.ObjType; +import com.xilinx.rapidwright.design.xdc.parser.ObjectGetterCommand; +import com.xilinx.rapidwright.design.xdc.parser.RegularEdifCellLookup; +import com.xilinx.rapidwright.design.xdc.parser.ResizePBlockCommand; +import com.xilinx.rapidwright.design.xdc.parser.SetPropertyCommand; +import com.xilinx.rapidwright.design.xdc.parser.UnsupportedGetterCommand; +import com.xilinx.rapidwright.design.xdc.parser.UnsupportedIfCommand; +import com.xilinx.rapidwright.design.xdc.parser.UnsupportedSetterCommand; +import com.xilinx.rapidwright.device.Device; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.util.FileTools; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.Namespace; +import tcl.lang.Resolver; +import tcl.lang.TCL; +import tcl.lang.TclException; +import tcl.lang.TclObject; +import tcl.lang.Var; +import tcl.lang.WrappedCommand; + + +/** + * Parses an XDC file for a limited subset of constraint types. It uses a full Tcl interpreter, so + * complex language constructs are possible. + *

+ * If the Parser encounters unsupported commands or command options, the parsed TCL code is converted back to strings + * and returned as UnsupportedConstraintElements. + *

+ * The parser can match cell references in constraints to modified designs. Users can supply their own + * {@link EdifCellLookup} to specify how to rewrite constraints. + *

+ * For a regular, non-rewritten netlist, users should use {@link RegularEdifCellLookup}. + *

+ * If no netlist is present, a null lookup will leave more complex get_cells calls unevaluated + * as unsupported constraints. + *

+ * Created on: Jul 27, 2015 + */ +public class XDCParser { + + + /** + * Create a tcl interpreter with XDC parsing commands + * + * Cell references are intentionally never disposed + * (see {@link com.xilinx.rapidwright.design.xdc.parser.TclHashIdentifiedObject}), so lifetime of this interpreter + * should be limited. + * + * @param constraints Constraints object to output to + * @param dev Device + * @param cellLookup the cell lookup (see {@link XDCParser class level documentation} for parameter details) + * @return interpreter + * @param lookup's cell representation + */ + public static Interp makeTclInterp(XDCConstraints constraints, Device dev, EdifCellLookup cellLookup) { + Interp interp = new Interp(); + interp.createCommand("set_property", new SetPropertyCommand<>(constraints, dev, cellLookup)); + interp.createCommand("current_design", new ObjectGetterCommand(cellLookup, false,ObjType.Design)); + + if (cellLookup!=null) { + interp.createCommand("get_cells", new GetCellsCommand<>(cellLookup)); + } else { + interp.createCommand("get_cells", new ObjectGetterCommand(cellLookup, true, ObjType.Cell)); + } + interp.createCommand("get_ports", new ObjectGetterCommand(cellLookup, true, ObjType.Port)); + interp.createCommand("get_pins", new ObjectGetterCommand(cellLookup, true, ObjType.Pin)); + interp.createCommand("get_pblocks", new ObjectGetterCommand(cellLookup, true, ObjType.PBlock)); + interp.createCommand("create_clock", new CreateClockCommand(constraints, cellLookup)); + + interp.createCommand("create_pblock", new CreatePBlockCommand(constraints)); + interp.createCommand("add_cells_to_pblock", new AddCellsToPblockCommand<>(constraints, cellLookup)); + interp.createCommand("resize_pblock", new ResizePBlockCommand(constraints, dev)); + + interp.createCommand("dump_objs", new DumpObjsCommand(cellLookup)); + + UnsupportedSetterCommand unsupportedSetterCommand = new UnsupportedSetterCommand(constraints, cellLookup, null); + interp.createCommand("set_false_path", unsupportedSetterCommand); + interp.createCommand("set_input_delay", unsupportedSetterCommand); + interp.createCommand("set_output_delay", unsupportedSetterCommand); + interp.createCommand("set_max_delay", unsupportedSetterCommand); + interp.createCommand("set_bus_skew", unsupportedSetterCommand); + interp.createCommand("current_instance", unsupportedSetterCommand); + interp.createCommand("set_clock_groups", unsupportedSetterCommand); + interp.createCommand("set_input_jitter", unsupportedSetterCommand); + interp.createCommand("set_clock_uncertainty", unsupportedSetterCommand); + + UnsupportedGetterCommand unsupportedGetterCommand = new UnsupportedGetterCommand(cellLookup); + interp.createCommand("get_clocks", unsupportedGetterCommand); + interp.createCommand("get_property", unsupportedGetterCommand); + interp.createCommand("get_nets", unsupportedGetterCommand); + interp.createCommand("all_fanout", unsupportedGetterCommand); + interp.createCommand("filter", unsupportedGetterCommand); + interp.createCommand("get_bels", unsupportedGetterCommand); + interp.createCommand("get_wires", unsupportedGetterCommand); + interp.createCommand("get_nodes", unsupportedGetterCommand); + UnsupportedGetterCommand.replaceInInterp(interp, cellLookup, "llength"); + UnsupportedGetterCommand.replaceInInterp(interp, cellLookup, "expr"); + + UnsupportedIfCommand.replaceInInterp(interp, constraints, cellLookup); + UnsupportedSetterCommand.replaceInInterp(interp, constraints, cellLookup, "foreach"); + + interp.createCommand("debugDump", new DebugDumpCommand()); + + //We need to allow [*] and bracketed numbers (e.h. [1] ) as suffix on quoted strings, so we need to hook into the command lookup + //This actually mirrors Vivado's behaviour very closely! Just enter * on Vivado's tcl prompt to see + interp.createCommand("wrapInput", new Command() { + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + interp.setResult("["+objv[0]+"]"); + } + }); + try { + WrappedCommand wrapInputCmd = Objects.requireNonNull(Namespace.findCommand(interp, "wrapInput", null, 0)); + interp.addInterpResolver("", new Resolver() { + @Override + public WrappedCommand resolveCmd(Interp interp, String name, Namespace context, int flags) throws TclException { + if (name.matches("\\d+") || name.equals("*")) { + return wrapInputCmd; + } + return null; + } + + @Override + public Var resolveVar(Interp interp, String name, Namespace context, int flags) throws TclException { + return null; + } + }); + } catch (TclException e) { + throw new RuntimeException(e); + } + + return interp; + } + + /** + * Parse XDC + * @param dev the device + * @param lines XDC content + * @param cellLookup the cell lookup (see {@link XDCParser class level documentation} for parameter details) + * @return parsed constraints + */ + public static XDCConstraints parseXDC(Device dev, List lines, EdifCellLookup cellLookup) { + XDCConstraints constraints = new XDCConstraints(); + + + Interp interp = makeTclInterp(constraints, dev, cellLookup); + try { + String data = String.join("\n", lines); + try { + interp.eval(data); + } catch (TclException ex) { + int code = ex.getCompletionCode(); + switch (code) { + case TCL.ERROR: + throw new RuntimeException(interp.getResult().toString()+" in line "+interp.getErrorLine(), ex); + case TCL.BREAK: + throw new RuntimeException( + "invoked \"break\" outside of a loop", ex); + case TCL.CONTINUE: + throw new RuntimeException( + "invoked \"continue\" outside of a loop", ex); + default: + throw new RuntimeException( + "command returned bad error code: " + code, ex); + } + } + } finally { + interp.dispose(); + } + + return constraints; + } + + + + /** + * @param fileName Name of the XDC file to parse + * @param dev the design + * @param cellLookup the cell lookup (see {@link XDCParser class level documentation} for parameter details) + * @return A map of port names to package pin information. + */ + public static XDCConstraints parseXDC(String fileName, Device dev, EdifCellLookup cellLookup){ + return parseXDC(dev, FileTools.getLinesFromTextFile(fileName), cellLookup); + } + + /** + * @param fileName Name of the XDC file to parse + * @param dev the design + * @param netlist optional netlist to enable more advanced get_cells calls + * @return A map of port names to package pin information. + */ + public static XDCConstraints parseXDC(String fileName, Device dev, EDIFNetlist netlist){ + return parseXDC(dev, FileTools.getLinesFromTextFile(fileName), new RegularEdifCellLookup(netlist)); + } + + /** + * @param fileName Name of the XDC file to parse + * @param design The design + * @return A map of port names to package pin information. + */ + public static XDCConstraints parseXDC(String fileName, Design design){ + return parseXDC(design.getDevice(), FileTools.getLinesFromTextFile(fileName), new RegularEdifCellLookup(design.getNetlist())); + } + + /** + * @param fileName Name of the XDC file to parse + * @param dev the device + * @return A map of port names to package pin information. + */ + public static XDCConstraints parseXDC(String fileName, Device dev){ + return parseXDC(dev, FileTools.getLinesFromTextFile(fileName), null); + } + + public static void writeXDC(List constraints, OutputStream out){ + if(constraints == null) return; + try { + for(String s : constraints){ + out.write(s.getBytes()); + out.write('\n'); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/AddCellsToPblockCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/AddCellsToPblockCommand.java new file mode 100644 index 000000000..61919d43e --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/AddCellsToPblockCommand.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.List; +import java.util.Objects; + +import com.xilinx.rapidwright.design.xdc.PBlockConstraint; +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +public class AddCellsToPblockCommand implements Command { + + private final XDCConstraints constraints; + private final EdifCellLookup cellLookup; + + public AddCellsToPblockCommand(XDCConstraints constraints, EdifCellLookup cellLookup) { + + this.constraints = constraints; + this.cellLookup = cellLookup; + } + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + + if (objv.length!=3) { + throw new RuntimeException("wrong argument count"); + } + DesignObject pblockDO = DesignObject.unwrapTclObject(interp, objv[1], cellLookup).orElseThrow(()->new RuntimeException("expected designObject")); + if (! (pblockDO instanceof NameDesignObject)) { + throw new RuntimeException("wrong argument type: "+pblockDO.getClass()); + } + NameDesignObject pblock = (NameDesignObject)pblockDO; + if (pblock.getType()!=ObjType.PBlock) { + throw new RuntimeException("wrong argument type: "+pblock.getType()); + } + + String pblockName = pblock.requireOneObject(); + PBlockConstraint pBlockConstraint = Objects.requireNonNull( + constraints.getPBlockConstraints().get(pblockName), + ()->"Did not find pblock "+pblockName + ); + + if (objv[2].toString().equals("-top")) { + pBlockConstraint.getCells().add(""); + return; + } + DesignObject cellsDO = DesignObject.requireUnwrapTclObject(interp, objv[2], cellLookup); + + if (cellsDO instanceof UnsupportedCmdResult) { + + List constraint = UnsupportedConstraintElement.commandToUnsupportedConstraints(interp, objv, cellLookup); + constraints.getUnsupportedConstraints().add(constraint); + + interp.resetResult(); + return; + } else if (cellsDO instanceof CellObject) { + for (T cell : ((CellObject) cellsDO).getCells()) { + String finalCell = cellLookup.getAbsoluteFinalName(cell); + pBlockConstraint.getCells().add(finalCell); + } + return; + } + + if (!(cellsDO instanceof NameDesignObject)) { + throw new RuntimeException("expected NameDesignObject but got "+cellsDO.getClass()+": "+cellsDO); + } + NameDesignObject cellsNDO = (NameDesignObject) cellsDO; + if (cellsNDO.getType()!=ObjType.Cell) { + throw new RuntimeException("expected CellObject but got "+cellsNDO.getType()); + } + for (String object : cellsNDO.getObjects()) { + pBlockConstraint.getCells().add(object); + } + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/CellObject.java b/src/com/xilinx/rapidwright/design/xdc/parser/CellObject.java new file mode 100644 index 000000000..16e38774e --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/CellObject.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; + +/** + * Tcl representation of a list of cells + * @param the cell representation + */ +public class CellObject extends DesignObject { + private final List cells; + private final EdifCellLookup cellLookup; + + public CellObject(List cells, EdifCellLookup cellLookup) { + this.cells = cells; + this.cellLookup = cellLookup; + } + + public String toXdc() { + if (cells.size()==1) { + String onlyCell = cellLookup.getAbsoluteFinalName(cells.get(0)); + if (cells.size() == 1 && !onlyCell.contains("[")) { + return "[get_cells " + onlyCell + "]"; + } + } + + return "[get_cells {" + cells.stream().map(cellLookup::getAbsoluteFinalName).collect(Collectors.joining(" ")) + "}]"; + } + + @Override + public Stream toUnsupportedConstraintElement() { + if (cells.isEmpty()) { + return Stream.of( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement("get_cells"), + new UnsupportedConstraintElement.SyntaxConstraintElement(" ["), + new UnsupportedConstraintElement.NameConstraintElement("list"), + new UnsupportedConstraintElement.SyntaxConstraintElement("]]") + ); + } + if (cells.size()==1) { + String onlyCell = cellLookup.getAbsoluteFinalName(cells.get(0)); + if (!onlyCell.contains("[")) { + return Stream.of( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement("get_cells"), + new UnsupportedConstraintElement.SyntaxConstraintElement(" "), + new UnsupportedConstraintElement.CellConstraintElement(onlyCell), + new UnsupportedConstraintElement.SyntaxConstraintElement("]") + ); + } + } + + Stream cellStream = cells.stream() + .map(cellLookup::getAbsoluteFinalName) + .map(UnsupportedConstraintElement.CellConstraintElement::new) + .flatMap(UnsupportedConstraintElement.addSpacesBetween()); + return UnsupportedConstraintElement.wrapStream(cellStream, Stream.of( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement("get_cells"), + new UnsupportedConstraintElement.SyntaxConstraintElement(" "), + new UnsupportedConstraintElement.SyntaxConstraintElement("{") + ), Stream.of( + + new UnsupportedConstraintElement.SyntaxConstraintElement("}"), + new UnsupportedConstraintElement.SyntaxConstraintElement("]") + )); + } + + public List getCells() { + return cells; + } + + @Override + public String toString() { + return cells.toString(); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/CreateClockCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/CreateClockCommand.java new file mode 100644 index 000000000..c2fd9d581 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/CreateClockCommand.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.List; + +import com.xilinx.rapidwright.design.xdc.ClockConstraint; +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * Tcl command: create_clock + */ +public class CreateClockCommand implements Command { + + private final XDCConstraints constraints; + private final EdifCellLookup cellLookup; + + public CreateClockCommand(XDCConstraints constraints, EdifCellLookup cellLookup) { + + this.constraints = constraints; + this.cellLookup = cellLookup; + } + + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + List ports = null; + String period = null; + String clockName = null; + for (int i = 1; i < objv.length; i++) { + if (objv[i].toString().startsWith("-")) { + switch (objv[i].toString()) { + case "-period": + period = objv[++i].toString(); + break; + case "-name": + clockName = objv[++i].toString(); + + break; + case "-waveform": + //Just skip the waveform specification + ++i; + break; + default: + throw new RuntimeException("expected -name | -period | -waveform but got " + objv[i]); + } + } else { + DesignObject obj = DesignObject.requireUnwrapTclObject(interp, objv[i], cellLookup); + if (obj instanceof UnsupportedCmdResult) { + + List constraint = UnsupportedConstraintElement.commandToUnsupportedConstraints(interp, objv, cellLookup); + constraints.getUnsupportedConstraints().add(constraint); + + interp.resetResult(); + return; + } + if (!(obj instanceof NameDesignObject) || (((NameDesignObject) obj).getType() != ObjType.Port && ((NameDesignObject) obj).getType() != ObjType.Pin)) { + throw new RuntimeException("expected port or pin but got " + obj.toXdc()+" (a "+obj.getClass()); + } + ports = ((NameDesignObject) obj).getObjects(); + + if (i + 1 != objv.length) { + throw new RuntimeException("Extra elements after port name"); + } + } + } + if (ports == null) { + throw new RuntimeException("did not have ports!"); + } + for (String port : ports) { + constraints.getClockConstraints().put(port, new ClockConstraint(clockName, Double.parseDouble(period), port)); + } + + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/CreatePBlockCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/CreatePBlockCommand.java new file mode 100644 index 000000000..d40b371eb --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/CreatePBlockCommand.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.Collections; + +import com.xilinx.rapidwright.design.xdc.PBlockConstraint; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +public class CreatePBlockCommand implements Command { + private final XDCConstraints constraints; + + public CreatePBlockCommand(XDCConstraints constraints) { + this.constraints = constraints; + } + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + if (objv.length!=2) { + throw new RuntimeException("wrong argument count"); + } + String pBlockName = objv[1].toString(); + if (constraints.getPBlockConstraints().containsKey(pBlockName)) { + throw new RuntimeException("duplicate pblock name: "+pBlockName); + } + PBlockConstraint constraint = new PBlockConstraint(); + constraint.getPblock().setName(pBlockName); + constraints.getPBlockConstraints().put(pBlockName, constraint); + NameDesignObject res = new NameDesignObject<>(ObjType.PBlock, Collections.singletonList(pBlockName)); + interp.setResult(TclHashIdentifiedObject.createReflectObject(interp, NameDesignObject.class, res)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/DebugDumpCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/DebugDumpCommand.java new file mode 100644 index 000000000..2ed865249 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/DebugDumpCommand.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.ReflectObject; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclObject; + +/** + * Tcl command to dump the arguments' internal representation + */ +public class DebugDumpCommand implements Command { + @Override + public void cmdProc(Interp interp, TclObject[] tclObjects) throws TclException { + debugDump(interp, tclObjects); + } + + public static void debugDump(Interp interp, TclObject[] tclObjects) { + for (int i = 0; i < tclObjects.length; i++) { + System.out.print("index "+i+": "); + debugDump(tclObjects[i], interp, " "); + } + } + + public static void debugDump(TclObject tclObject, Interp interp, String indent) { + if (tclObject.getInternalRep() instanceof TclList) { + System.out.println("list"); + try { + TclObject[] items = TclList.getElements(interp, tclObject); + for (int i = 0; i < items.length; i++) { + System.out.print(indent+" index "+i+": "); + debugDump(items[i], interp,indent+" "); + } + } catch (TclException e) { + throw new RuntimeException(e); + } + } else if (tclObject.getInternalRep() instanceof ReflectObject) { + try { + Object refl = ReflectObject.get(interp, tclObject); + System.out.println("reflect object "+tclObject+" "+refl); + } catch (TclException e) { + throw new RuntimeException(e); + } + } else { + System.out.println(tclObject.getInternalRep().getClass().getName()+": "+tclObject); + } + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/DesignObject.java b/src/com/xilinx/rapidwright/design/xdc/parser/DesignObject.java new file mode 100644 index 000000000..833ff4717 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/DesignObject.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import tcl.lang.Interp; +import tcl.lang.ReflectObject; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclObject; + +/** + * Base class for any object that will be referenced from Tcl + * @param Representation of Cells + */ +public abstract class DesignObject { + /** + * Casting helper: + *

    + *
  • If the object is an instance of the cell class (T), casts and wraps it into a DesignObject o
  • + *
  • Casts DesignObjects
  • + *
the lookup's cell representation + */ + public static DesignObject requireCastUnwrappedObject(Object obj, EdifCellLookup lookup) { + if (lookup != null && lookup.getCellClass().isInstance(obj)) { + return new CellObject(Collections.singletonList(lookup.castCellInst(obj)), lookup); + } + return (DesignObject) obj; + } + + /** + * Try to convert a TclObject into the DesignObject it represents + * @param interp the interpreter + * @param obj the object to unwrap + * @param lookup the cell lookup + * @return the design object or an empty optional if it isn't one + * @param the lookup's cell representation + * @throws TclException + */ + public static Optional> unwrapTclObject(Interp interp, TclObject obj, EdifCellLookup lookup) throws TclException { + if (obj.getInternalRep() instanceof TclList) { + TclObject[] elements = TclList.getElements(interp, obj); + if (!Arrays.stream(elements).allMatch(e-> { + try { + return e.getInternalRep() instanceof ReflectObject &&lookup.getCellClass().isInstance(ReflectObject.get(interp,e)); + } catch (TclException ex) { + throw new RuntimeException(ex); + } + })) { + return Optional.empty(); + } + List cells = Arrays.stream(elements).map(x -> { + try { + return lookup.castCellInst(ReflectObject.get(interp, x)); + } catch (TclException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + return Optional.of(new CellObject<>(cells, lookup)); + } + if (obj.getInternalRep() instanceof ReflectObject) { + return Optional.of(requireCastUnwrappedObject(ReflectObject.get(interp, obj), lookup)); + } + return Optional.empty(); + } + + private static String objDebugInfo(Interp interp, TclObject obj) { + try { + if (obj.getInternalRep() instanceof ReflectObject) { + Object ref = ReflectObject.get(interp, obj); + return " is reflect object type " + ref.getClass() + ": " + ref; + } else if (obj.getInternalRep() instanceof TclList) { + return " is list: ["+ Arrays.stream(TclList.getElements(interp, obj)).map(o->objDebugInfo(interp,o)).collect(Collectors.joining(", "))+"]"; + } else { + return " internal rep: "+obj.getInternalRep().getClass(); + } + } catch (TclException e) { + throw new RuntimeException(e); + } + } + + /** + * Convert a TclObject into the DesignObject it represents or throw if it isn't one + * @param interp the interpreter + * @param obj the object to unwrap + * @param lookup the cell lookup + * @return the design object + * @param the lookup's cell representation + * @throws TclException + */ + public static DesignObject requireUnwrapTclObject(Interp interp, TclObject obj, EdifCellLookup lookup) throws TclException { + return unwrapTclObject(interp, obj, lookup) + .orElseThrow(()-> { + String moreInfo = objDebugInfo(interp, obj); + return new IllegalArgumentException("expected DesignObject but got " + obj + moreInfo); + }); + + } + + public abstract String toXdc(); + + public abstract Stream toUnsupportedConstraintElement(); +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/DumpObjsCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/DumpObjsCommand.java new file mode 100644 index 000000000..cb99515d6 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/DumpObjsCommand.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.Optional; + +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * Tcl command to dump the arguments' as unwrapped DesignObjects + */ +public class DumpObjsCommand implements Command { + private final EdifCellLookup cellLookup; + public DumpObjsCommand(EdifCellLookup cellLookup) { + this.cellLookup = cellLookup; + } + + + + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + TclObject tclObject = objv[1]; + Optional obj = DesignObject.unwrapTclObject(interp, tclObject, cellLookup); + if (obj.isPresent()) { + System.out.println(obj.get()); + } else { + System.out.println("no java obj: "+tclObject); + } + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/EdifCellLookup.java b/src/com/xilinx/rapidwright/design/xdc/parser/EdifCellLookup.java new file mode 100644 index 000000000..47690418c --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/EdifCellLookup.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.Arrays; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFTools; +import org.apache.commons.io.FilenameUtils; +import org.jetbrains.annotations.NotNull; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * Lookup Cells for use in the XDC Parser. + *

+ * Cells can have different names in the source XDC versus the actual objects in the design. This allows rewriting + * constraints to match a possibly restructured design. Cells have an original and a final name to + * support this. + *

+ * A derived class can specify how to do this mapping. {@link RegularEdifCellLookup} operates on EDIF netlists without + * rewriting. + * + * @param Representation of Cells + */ +public abstract class EdifCellLookup { + + + /** + * Convert a cell to a Tcl Object + * @param interp the interpreter + * @param cell the cell + * @return Tcl object representing the cell + * @throws TclException + */ + @NotNull + public TclObject toReflectObj(Interp interp, T cell) throws TclException { + return TclHashIdentifiedObject.createReflectObject(interp, getCellClass(), cell); + } + + /** + * Stream of the tree of cells rooted at the current cell + * @param ci base of the tree + * @return stream of all children + */ + public Stream allCellInsts(T ci) { + Stream self = Stream.of(ci); + Stream directChildren = getChildrenOf(ci);; + Stream allChildren = directChildren.flatMap(this::allCellInsts); + return Stream.concat(self, allChildren); + } + + public Stream getHierCellInstsFromWildcardName(String cellName) { + if (!cellName.contains("*")) { + T res = getInstFromOriginalName(cellName); + if (res == null) { + return Stream.empty(); + } + return Stream.of(res); + } + return getChildBySomeAbsoluteName(cellName, (s, item) -> FilenameUtils.wildcardMatch(getRelativeOriginalName(item), s)); + } + + public Stream getHierCellInstsFromRegexpName(String cellName) { + return getChildBySomeAbsoluteName(cellName, (s, item) -> getRelativeOriginalName(item).matches(s)); + } + + public abstract T getInstFromOriginalName(String cellName); + + public abstract Stream getChildrenOf(T f); + + public abstract T getChild(T cell, String name); + + private Stream getChildBySomeAbsoluteNameWorker(String[] parts, int level, T current, BiPredicate filter) { + if (level==parts.length) { + return Stream.of(current); + } + return getChildrenOf(current) + .filter(child -> filter.test(parts[level], child)) + .flatMap(c->getChildBySomeAbsoluteNameWorker(parts, level+1, c, filter)); + } + + + public Stream getChildBySomeAbsoluteName(String name, BiPredicate filter) { + + if (name.isEmpty()) return Stream.of(getRoot()); + + String[] parts = name.split(EDIFTools.EDIF_HIER_SEP); + + // Sadly, cells can be named 'fred/' instead of 'fred', this code handles this situation + if (name.charAt(name.length()-1) == '/') { + parts[parts.length-1] = parts[parts.length-1] + EDIFTools.EDIF_HIER_SEP; + } + + return getChildBySomeAbsoluteNameWorker(parts, 0, getRoot(), filter); + } + + + + + public Predicate getAbsoluteRegexFilter(String cellNames) { + return eci -> getAbsoluteOriginalName(eci).matches(cellNames); + } + + public Predicate getAbsoluteWildcardFilter(String cellNames) { + return eci -> FilenameUtils.wildcardMatch(getAbsoluteOriginalName(eci), cellNames); + } + + public Stream getAllCellInsts() { + return allCellInsts(getRoot()); + } + + public abstract EDIFHierCellInst toEdifHierCellInst(T cell); + + public Predicate getCellTypeFilter(Set values) { + return ci -> values.contains(getCellType(ci)); + } + + public abstract T getRoot(); + + public abstract String getAbsoluteFinalName(T current); + public abstract String getRelativeFinalName(T current); + public abstract String getAbsoluteOriginalName(T current); + public abstract String getRelativeOriginalName(T current); + + public abstract String getCellType(T current); + + public abstract Class getCellClass(); + + public T castCellInst(Object obj) { + return (T)getCellClass().cast(obj); + } + + public abstract T getInstFromFinalName(String s); + + public String getOriginalName(String s) { + return getAbsoluteOriginalName(getInstFromFinalName(s)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/GetCellsCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/GetCellsCommand.java new file mode 100644 index 000000000..2cfe93d03 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/GetCellsCommand.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import com.xilinx.rapidwright.util.Pair; +import tcl.lang.Command; +import tcl.lang.ExprValue; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclObject; + +/** + * Tcl command: get_cells + */ +public class GetCellsCommand implements Command { + + private final EdifCellLookup cellLookup; + + public GetCellsCommand(EdifCellLookup cellLookup) { + this.cellLookup = cellLookup; + } + + @Override + public void cmdProc(Interp interp, TclObject[] argv) throws TclException { + boolean hierFlag = false; + boolean regexpFlag = false; + TclObject filter = null; + TclObject cellName = null; + + for (int i = 1; i < argv.length; i++) { + String item = argv[i].toString(); + switch (item) { + case "-hier": + case "-hierarchical": + hierFlag = true; + break; + case "-filter": + filter = argv[++i]; + break; + case "-regexp": + regexpFlag = true; + break; + case "-quiet": + //Ignore + break; + case "-of_objects": + case "-of": + interp.setResult(UnsupportedCmdResult.makeTclObj(interp, argv, cellLookup, false, false)); + return; + default: + if (cellName != null) { + String s = new UnsupportedCmdResult<>(interp, argv, cellLookup, false, false).toString(); + throw new RuntimeException("Duplicate cell-name string or unsupported flag: "+s); + } + cellName = argv[i]; + break; + } + + } + + if (cellName != null && !regexpFlag && filter == null && !hierFlag) { + simpleGetCells(interp, cellName); + } else { + String cellNameStr = cellName != null ? TclHashIdentifiedObject.unpackAsString(interp, cellName.toString(), cellLookup) : null; + if (!complexGetCells(interp, hierFlag, regexpFlag, cellNameStr, filter, argv)) { + interp.setResult(UnsupportedCmdResult.makeTclObj(interp, argv, cellLookup, false, false)); + } + } + } + + /** + * This only supports OR-in together different == clauses. + * + * @param interp + * @param expr + * @return + */ + private static Map> parseFilterExpression(Interp interp, String expr) { + //Remove unneeded parens + if (expr.matches("\\([^)]*\\)")) { + expr = expr.substring(1, expr.length() - 1); + } + if (expr.contains("&&") || expr.contains("(")) { + try { + ExprValue exprValue = interp.evalExpression(expr.toString()); + } catch (TclException e) { + //Ignore + } + return null; //Give up + } + String[] split = expr.split("\\s*\\|\\|\\s*"); + + Map> oredClauses = new HashMap<>(); + for (String s : split) { + String[] clause = s.split("\\s*==\\s*"); + if (clause.length != 2) { + return null; //Give up + } + oredClauses.computeIfAbsent(clause[0], x -> new HashSet<>()).add(clause[1]); + } + + Set refname = oredClauses.get("REF_NAME"); + Set origrefname = oredClauses.get("ORIG_REF_NAME"); + if (!Objects.equals(refname, origrefname)) { + throw new RuntimeException("expression too complex: " + expr); + } + oredClauses.remove("ORIG_REF_NAME"); + + return oredClauses; + } + + private static int cellDebugCallCout = 0; + + private boolean complexGetCells(Interp interp, boolean hierFlag, boolean regexFlag, String cellNames, TclObject filterArg, TclObject[] argv) throws TclException { + Stream candidateSupplier = null; + List, Function>> filter = new ArrayList<>(); + + if (!hierFlag) { + return false; + } + + if (filterArg != null) { + Map> s = parseFilterExpression(interp, filterArg.toString()); + if (s==null) { + return false; + } + + if (s.size() > 1) { + return false; + } + String entryType = s.keySet().iterator().next(); + Set values = s.get(entryType); + if (Objects.equals(entryType, "REF_NAME")) { + filter.add(new Pair<>(cellLookup.getCellTypeFilter(values), e -> { + return "checked if " + cellLookup.getCellType(e) + " is in " + values; + })); + } else if (Objects.equals(entryType, "PARENT")) { + List roots = values.stream() + .map(r-> TclHashIdentifiedObject.unpackAsString(interp, r, cellLookup)) + .flatMap(cellLookup::getHierCellInstsFromWildcardName) + .collect(Collectors.toList()); + candidateSupplier = roots.stream().flatMap(cellLookup::getChildrenOf); + System.out.println("Supplying candidates from direct children of " + roots + ". raw roots: " + values); + } + } + + if (cellNames != null) { + if (regexFlag) { + if (candidateSupplier == null) { + candidateSupplier = cellLookup.getHierCellInstsFromRegexpName(cellNames); + } else { + filter.add(new Pair<>(cellLookup.getAbsoluteRegexFilter(cellNames), eci -> "Checking if " + eci + " matches regex " + cellNames)); + } + } else { + if (candidateSupplier == null) { + candidateSupplier = cellLookup.getHierCellInstsFromWildcardName(cellNames); + } else { + filter.add(new Pair<>(cellLookup.getAbsoluteWildcardFilter(cellNames), eci -> "Checking if " + eci + " matches wildcard " + cellNames)); + } + } + } + + if (candidateSupplier == null) { + System.out.println("Supplying candidates from all cells"); + candidateSupplier = cellLookup.getAllCellInsts(); + } + boolean debugFiltering = false; + List cells; + if (!debugFiltering) { + Stream resultStream = candidateSupplier; + for (Pair,?> filterStage : filter) { + resultStream = resultStream.filter(filterStage.getFirst()); + } + cells = resultStream.collect(Collectors.toList()); + } else{ + Path debugFile = Paths.get("./debugCellFiltering/"+(cellDebugCallCout++)+".txt"); + try { + Files.createDirectories(debugFile.getParent()); + } catch (IOException e) { + throw new RuntimeException(e); + } + System.out.println("Writing debug info to "+debugFile); + try (PrintStream ps = new PrintStream(Files.newOutputStream(debugFile))) { + ps.println("Debugging for "+ Arrays.toString(argv)); + + ps.println("hierFlag = " + hierFlag); + ps.println("regexFlag = " + regexFlag); + ps.println("cellNames = " + cellNames); + ps.println("filterArg = " + filterArg); + + cells = new ArrayList<>(); + Iterable iterable = candidateSupplier::iterator; + for (T eci : iterable) { + ps.println("checking eci: " + eci); + boolean in = true; + for (int i = 0; i < filter.size(); i++) { + Predicate f = filter.get(i).getFirst(); + boolean test = f.test(eci); + if (!test) { + ps.println("\tFiltered out by filter " + i + ": " + filter.get(i).getSecond().apply(eci)); + in = false; + break; + } else { + ps.println("\taccepted by filter " + i + ": " + filter.get(i).getSecond().apply(eci)); + } + } + if (in) { + ps.println("\tadding!"); + cells.add(eci); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + TclObject list = TclList.newInstance(); + if (cells.isEmpty()) { + System.out.println("did not find cell for call "+Arrays.toString(argv)); + } else { + cells.stream().sorted(Comparator.comparing(cellLookup::getAbsoluteOriginalName)) + .forEach(cell-> { + try { + TclList.append(interp, list, cellLookup.toReflectObj(interp, cell)); + } catch (TclException e) { + throw new RuntimeException(e); + } + }); + } + interp.setResult(list); + return true; + } + + + private void simpleGetCells(Interp interp, TclObject cellName) throws TclException { + if (cellName.getInternalRep() instanceof TclList) { + System.out.println("started get cells list"); + TclObject[] elements = TclList.getElements(interp, cellName); + String[] cellNames = new String[elements.length]; + for (int i=0; i cells = cellLookup.getHierCellInstsFromWildcardName(cellName).collect(Collectors.toList()); + if (cells.isEmpty()) { + System.out.println("did not find cell for " + cellName); + } else { + cells.sort(Comparator.comparing(cellLookup::getAbsoluteOriginalName)); + for (T cell : cells) { + TclList.append(interp, list, cellLookup.toReflectObj(interp, cell)); + } + } + } + interp.setResult(list); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/NameDesignObject.java b/src/com/xilinx/rapidwright/design/xdc/parser/NameDesignObject.java new file mode 100644 index 000000000..fa219ff99 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/NameDesignObject.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; + +public class NameDesignObject extends DesignObject { + private final ObjType type; + private final List objects; + + public NameDesignObject(ObjType type, List objects) { + this.type = type; + this.objects = objects; + } + + @Override + public String toString() { + return "DesignObjects{" + + "type='" + type + '\'' + + ", objects=" + objects + + '}'; + } + + public String toXdc() { + return UnsupportedConstraintElement.toXdc(toUnsupportedConstraintElement()); + } + + @Override + public Stream toUnsupportedConstraintElement() { + if (objects == null) { + return Stream.of( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement(type.getXdcCommand()), + new UnsupportedConstraintElement.SyntaxConstraintElement("]") + ); + } + + if (objects.isEmpty()) { + return Stream.of( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement(type.getXdcCommand()), + new UnsupportedConstraintElement.SyntaxConstraintElement(" ["), + new UnsupportedConstraintElement.NameConstraintElement("list"), + new UnsupportedConstraintElement.SyntaxConstraintElement("]]") + ); + + } + + boolean braces = objects.size()!=1 || objects.stream().anyMatch(XDCTools::stringNeedsBraces); + + List before = new ArrayList<>(Arrays.asList( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement(type.getXdcCommand()), + new UnsupportedConstraintElement.SyntaxConstraintElement(" ") + )); + List after = new ArrayList<>(Collections.singleton( + + new UnsupportedConstraintElement.SyntaxConstraintElement("]") + )); + if (braces) { + before.add(new UnsupportedConstraintElement.SyntaxConstraintElement("{")); + after.add(0, new UnsupportedConstraintElement.SyntaxConstraintElement("}")); + } + + Function uceConstructor = type == ObjType.Cell ? UnsupportedConstraintElement.CellConstraintElement::new : UnsupportedConstraintElement.NameConstraintElement::new; + Stream objs = objects.stream().map(uceConstructor).flatMap(UnsupportedConstraintElement.addSpacesBetween()); + return UnsupportedConstraintElement.wrapStream(objs, before.stream(), after.stream()); + } + + public ObjType getType() { + return type; + } + + public List getObjects() { + return objects; + } + + public String requireOneObject() { + if (objects.size()!=1) { + throw new RuntimeException("requiring one object but got: "+objects); + } + return objects.get(0); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/ObjType.java b/src/com/xilinx/rapidwright/design/xdc/parser/ObjType.java new file mode 100644 index 000000000..bceed43af --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/ObjType.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +public enum ObjType { + Design("current_design"), + Cell("get_cells"), + Port("get_ports"), + Pin("get_pins"), + PBlock("get_pblocks"); + + private final String xdcCommand; + + ObjType(String xdcCommand) { + this.xdcCommand = xdcCommand; + } + + public String getXdcCommand() { + return xdcCommand; + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/ObjectGetterCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/ObjectGetterCommand.java new file mode 100644 index 000000000..66e223178 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/ObjectGetterCommand.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.ReflectObject; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclNumArgsException; +import tcl.lang.TclObject; + +/** + * Tcl command to get something that will probably be passed onto a set_property call later + */ +public class ObjectGetterCommand implements Command { + + private final EdifCellLookup lookup; + private final boolean takesObjects; + private final ObjType objType; + + public ObjectGetterCommand(EdifCellLookup lookup, boolean takesObjects, ObjType objType) { + this.lookup = lookup; + this.takesObjects = takesObjects; + + this.objType = objType; + } + + @Override + public void cmdProc(Interp interp, TclObject[] argv) throws TclException { + DesignObject res; + if (!takesObjects) { + if (argv.length != 1) { + throw new TclNumArgsException(interp, 1, argv, ""); + } + res = new NameDesignObject(objType, null); + } else { + boolean argCountOk = argv.length == 2 || (argv.length==3 && argv[1].toString().equals("-quiet")); + if (!argCountOk || TclHashIdentifiedObject.containsStringifiedObject(argv)) { + interp.setResult(UnsupportedCmdResult.makeTclObj(interp, argv, lookup, false, false)); + return; + } + + TclObject objs = argv[argv.length-1]; + if (objs.getInternalRep() instanceof TclList) { + TclObject[] elements = TclList.getElements(interp, objs); + List strings = Arrays.stream(elements).map(Object::toString).collect(Collectors.toList()); + res = new NameDesignObject(objType, strings); + } else { + res = new NameDesignObject(objType, Arrays.asList(argv[1].toString().split(" "))); + } + } + interp.setResult(TclHashIdentifiedObject.createReflectObject(interp, res.getClass(), res)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/RegularEdifCellLookup.java b/src/com/xilinx/rapidwright/design/xdc/parser/RegularEdifCellLookup.java new file mode 100644 index 000000000..91aba9352 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/RegularEdifCellLookup.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.stream.Stream; + +import com.xilinx.rapidwright.edif.EDIFCellInst; +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.edif.EDIFPropertyValue; + +/** + * Default cell lookup for normal designs. + *

+ * This assumes that the EDIFNetlist matches what a regular synthesis run would produce. No rewriting of cell names + * is performed. + */ +public class RegularEdifCellLookup extends EdifCellLookup { + + private final EDIFNetlist netlist; + + public RegularEdifCellLookup(EDIFNetlist netlist) { + this.netlist = netlist; + } + + @Override + public Stream getChildrenOf(EDIFHierCellInst f) { + return f.getCellType().getCellInsts().stream().map(f::getChild); + } + + @Override + public EDIFHierCellInst getChild(EDIFHierCellInst cell, String name) { + EDIFCellInst child = cell.getCellType().getCellInst(name); + if (child==null) { + return null; + } + return cell.getChild(child); + } + + @Override + public EDIFHierCellInst toEdifHierCellInst(EDIFHierCellInst cell) { + return cell; + } + + + @Override + public EDIFHierCellInst getRoot() { + return netlist.getTopHierCellInst(); + } + + @Override + public String getAbsoluteFinalName(EDIFHierCellInst current) { + return current.getFullHierarchicalInstName(); + } + + @Override + public String getRelativeFinalName(EDIFHierCellInst current) { + return current.getInst().getName(); + } + + @Override + public String getAbsoluteOriginalName(EDIFHierCellInst current) { + return current.getFullHierarchicalInstName(); + } + + @Override + public String getRelativeOriginalName(EDIFHierCellInst current) { + return current.getInst().getName(); + } + + @Override + public String getCellType(EDIFHierCellInst current) { + EDIFPropertyValue prop = current.getCellType().getProperty("ORIG_REF_NAME"); + if (prop != null) { + return prop.getValue(); + } + return current.getCellType().getName(); + } + + @Override + public Class getCellClass() { + return EDIFHierCellInst.class; + } + + @Override + public EDIFHierCellInst getInstFromOriginalName(String cellName) { + return netlist.getHierCellInstFromName(cellName); + } + + @Override + public EDIFHierCellInst getInstFromFinalName(String s) { + return netlist.getHierCellInstFromName(s); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/ResizePBlockCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/ResizePBlockCommand.java new file mode 100644 index 000000000..9dbc2a64b --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/ResizePBlockCommand.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.Objects; + +import com.xilinx.rapidwright.design.blocks.PBlockRange; +import com.xilinx.rapidwright.design.xdc.PBlockConstraint; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import com.xilinx.rapidwright.device.Device; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +public class ResizePBlockCommand implements Command { + private final XDCConstraints constraints; + private Device dev; + + public ResizePBlockCommand(XDCConstraints constraints, Device dev) { + this.constraints = constraints; + this.dev = dev; + } + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + String pblockName = null; + String toAdd = null; + for (int i = 1; i < objv.length; i++) { + if (objv[i].toString().startsWith("-")) { + switch (objv[i].toString()) { + case "-quiet": + case "-verbose": + break; + case "-add": + if (toAdd != null) { + throw new RuntimeException("multiple -add calls"); + } + toAdd = objv[++i].toString(); + break; + default: + throw new RuntimeException("unsupported resize_block mode " + objv[i]); + } + } else { + DesignObject obj = DesignObject.requireUnwrapTclObject(interp, objv[i], null); + if (!(obj instanceof NameDesignObject) || ((NameDesignObject) obj).getType() != ObjType.PBlock) { + throw new RuntimeException("expected pblock but got " + obj.toXdc()); + } + pblockName = ((NameDesignObject) obj).requireOneObject(); + } + } + + if (pblockName == null) { + throw new RuntimeException("pblock name is null"); + } + PBlockConstraint constraint = Objects.requireNonNull(constraints.getPBlockConstraints().get(pblockName)); + constraint.getPblock().add(new PBlockRange(dev, toAdd)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/SetPropertyCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/SetPropertyCommand.java new file mode 100644 index 000000000..230f11e29 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/SetPropertyCommand.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.PBlockConstraint; +import com.xilinx.rapidwright.device.Device; +import com.xilinx.rapidwright.design.xdc.PackagePinConstraint; +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * Tcl command: set_property + * @param + */ +public class SetPropertyCommand implements Command { + private final XDCConstraints constraints; + private final Device dev; + private final EdifCellLookup cellLookup; + + public SetPropertyCommand(XDCConstraints constraints, Device dev, EdifCellLookup cellLookup) { + this.constraints = constraints; + this.dev = dev; + this.cellLookup = cellLookup; + } + + public void cmdProc(Interp interp, TclObject[] argv) + throws TclException { + String k = argv[1].toString(); + String v = argv[2].toString(); + DesignObject obj = DesignObject.requireUnwrapTclObject(interp, argv[argv.length - 1], cellLookup); + + if (k.equals("-dict")) { + String[] items = v.split(" "); + if (items.length % 2 != 0) { + throw new TclException(interp, "found odd number of arguments in kv-dict"); + } + for (int i = 0; i < items.length; i += 2) { + storeKV(items[i], items[i + 1], obj); + } + } else { + storeKV(k, v, obj); + } + + interp.setResult(0); + } + + private void eachPin(NameDesignObject pins, BiConsumer setter, String value) { + for (String object : pins.getObjects()) { + PackagePinConstraint ppc = constraints.getPinConstraints().computeIfAbsent(object, PackagePinConstraint::new); + setter.accept(ppc, value); + } + } + + private void addUnsupportedKV(String k, String v, DesignObject someObj) { + boolean needsBraces = XDCTools.stringNeedsBraces(v); + Stream line = Stream.concat( + Stream.of( + new UnsupportedConstraintElement.NameConstraintElement("set_property"), + new UnsupportedConstraintElement.SyntaxConstraintElement(" "), + new UnsupportedConstraintElement.NameConstraintElement(k), + new UnsupportedConstraintElement.SyntaxConstraintElement(needsBraces ? " {" : " "), + new UnsupportedConstraintElement.NameConstraintElement(v), + new UnsupportedConstraintElement.SyntaxConstraintElement(needsBraces ? "} " : " ") + ), + someObj.toUnsupportedConstraintElement() + ); + List l = line.collect(Collectors.toList()); + constraints.getUnsupportedConstraints().add(l); + } + + private void storeKV(String k, String v, DesignObject someObj) { + if (someObj instanceof CellObject) { + for (T cell : ((CellObject) someObj).getCells()) { + constraints.getCellProperties().computeIfAbsent(cellLookup.getAbsoluteFinalName(cell), x -> new HashMap<>()).put(k, v); + } + return; + } + if (someObj instanceof UnsupportedCmdResult) { + addUnsupportedKV(k, v, someObj); + return; + } + if (!(someObj instanceof NameDesignObject)) { + throw new RuntimeException("expected NameDesignObject but got "+someObj.getClass()+": "+someObj); + } + NameDesignObject obj = (NameDesignObject) someObj; + switch (obj.getType()) { + case Design: + addUnsupportedKV(k, v, obj); + break; + case Cell: + if (k.equals("ASYNC_REG")) { + addUnsupportedKV(k,v,obj); + } else { + for (String object : obj.getObjects()) { + constraints.getCellProperties().computeIfAbsent(object, x -> new HashMap<>()).put(k, v); + } + } + break; + case Port: + switch (k) { + case "IOSTANDARD": + eachPin(obj, PackagePinConstraint::setIOStandard, v); + break; + case "PACKAGE_PIN": + case "LOC": + if (dev !=null && !dev.getActivePackage().getPackagePinMap().containsKey(v)) { + throw new RuntimeException("Invalid pin for " + obj + ": " + v); + } + eachPin(obj, PackagePinConstraint::setPackagePin, v); + break; + default: + addUnsupportedKV(k, v, obj); + + } + break; + case PBlock: + PBlockConstraint pBlockConstraint = Objects.requireNonNull(constraints.getPBlockConstraints().get(obj.requireOneObject())); + switch (k) { + case "CONTAIN_ROUTING": + pBlockConstraint.getPblock().setContainRouting(parseBool(v)); + break; + case "IS_SOFT": + pBlockConstraint.getPblock().setIsSoft(parseBool(v)); + break; + case "EXCLUDE_PLACEMENT": + pBlockConstraint.getPblock().setExcludePlacement(parseBool(v)); + break; + default: + throw new RuntimeException("Trying to set unknown property "+k+" on pblock "+obj.requireOneObject()); + } + break; + default: + throw new RuntimeException("Unexpected obj type "+obj.getType()); + } + } + + private boolean parseBool(String v) { + switch (v.toLowerCase()) { + case "true": + case "1": + return true; + case "false": + case "0": + return false; + default: + throw new RuntimeException("invalid bool: "+v); + } + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/TclHashIdentifiedObject.java b/src/com/xilinx/rapidwright/design/xdc/parser/TclHashIdentifiedObject.java new file mode 100644 index 000000000..c3d457e11 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/TclHashIdentifiedObject.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.Arrays; +import java.util.function.Consumer; + +import tcl.lang.Interp; +import tcl.lang.ReflectObject; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * Helper methods for registering java objects via their identity hash code and looking them up. + */ +public class TclHashIdentifiedObject { + private static final String NAME_START = "+%%javahashstart%%+"; + private static final String NAME_END = "+%%javahashend%%+"; + + public static TclObject createReflectObject(Interp interp, Class clazz, Object obj) throws TclException { + String customRefID = NAME_START + ReflectObject.getHashString(clazz, obj) + NAME_END; + TclObject tclObject = ReflectObject.newInstance(interp, clazz, obj, customRefID); + // Normally, Tcl objects are removed from internal table once no variable refers to them. + // We want to keep them around till the end of life of the interpreter (since a stringified reference may still exist) + // Therefore, increase reference count by 1 + tclObject.preserve(); + return tclObject; + } + + public static void unpack(Interp interp, String s, Consumer outputString, Consumer outputObject) { + int offset = 0; + while (offset < s.length()) { + int objStart = s.indexOf(NAME_START, offset); + if (objStart==-1) { + outputString.accept(s.substring(offset, s.length())); + break; + } + if (objStart>offset) { + outputString.accept(s.substring(offset, objStart)); + } + int objEnd = s.indexOf(NAME_END, objStart); + + String objName = s.substring(objStart+NAME_START.length(), objEnd); + Object obj = ReflectObject.findObjectByHash(interp, objName); + if (obj==null) { + throw new RuntimeException("Did not find hash identified object "+objName+". Was this mistakenly freed?"); + } + outputObject.accept(obj); + + offset = objEnd+NAME_END.length(); + } + } + + public static boolean containsStringifiedObject(String s) { + return s.contains(NAME_START); + } + + public static boolean containsStringifiedObject(TclObject[] args) { + return Arrays.stream(args).anyMatch(o->containsStringifiedObject(o.toString())); + } + + public static String unpackAsString(Interp interp, String s, EdifCellLookup lookup) { + StringBuilder sb = new StringBuilder(); + unpack(interp, s, sb::append, obj -> { + T t = lookup.castCellInst(obj); + sb.append(lookup.getAbsoluteOriginalName(t)); + }); + return sb.toString(); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedCmdResult.java b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedCmdResult.java new file mode 100644 index 000000000..fa3bdb72c --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedCmdResult.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * This wraps an unsupported command's call to support passing it on + */ +public class UnsupportedCmdResult extends DesignObject{ + private final List cmd; + + private final EdifCellLookup lookup; + + public UnsupportedCmdResult(List cmd, EdifCellLookup lookup) { + this.cmd = cmd; + this.lookup = lookup; + } + + private static Function> wrapDollarSigns() { + final boolean[] inBraces = {false}; + return uce -> { + if (uce instanceof UnsupportedConstraintElement.CellConstraintElement || uce instanceof UnsupportedConstraintElement.NameConstraintElement) { + if (!inBraces[0] && uce.toXdc().contains("$")) { + inBraces[0] = true; + return Stream.of(new UnsupportedConstraintElement.SyntaxConstraintElement("{"), uce); + } + return Stream.of(uce); + } else { + String s = uce.toXdc(); + StringBuilder sb = new StringBuilder(); + for (char c : s.toCharArray()) { + switch (c) { + case '{': + inBraces[0] = true; + sb.append(c); + break; + case '}': + inBraces[0] = false; + sb.append(c); + break; + case ']': + if (inBraces[0]) { + sb.append('}'); + inBraces[0] = false; + } + sb.append(c); + break; + default: + sb.append(c); + break; + } + } + return Stream.of(new UnsupportedConstraintElement.SyntaxConstraintElement(sb.toString())); + } + }; + } + + public UnsupportedCmdResult(Interp interp, TclObject[] argv, EdifCellLookup lookup, boolean replaceProbableCells, boolean applyWildcardsChooseAny) { + this.lookup = lookup; + Stream objs = Arrays.stream(argv) + .flatMap(UnsupportedConstraintElement.addSpacesBetween( + obj->UnsupportedConstraintElement.objToUnsupportedConstraintElement(interp, obj, lookup, replaceProbableCells, applyWildcardsChooseAny) + )); + this.cmd = UnsupportedConstraintElement.wrapStream(objs, "[", "]") + .flatMap(wrapDollarSigns()) + .collect(Collectors.toList()); + } + + public static TclObject makeTclObj(Interp interp, TclObject[] objv, EdifCellLookup lookup, boolean replaceProbableCells, boolean applyWildcardsChooseAny) throws TclException { + return new UnsupportedCmdResult<>(interp, objv, lookup, replaceProbableCells, applyWildcardsChooseAny).toReflectObj(interp); + } + + @Override + public String toString() { + return toXdc(); + } + + @Override + public String toXdc() { + return UnsupportedConstraintElement.toXdc(cmd.stream()); + } + + @Override + public Stream toUnsupportedConstraintElement() { + return cmd.stream(); + } + + public TclObject toReflectObj(Interp interp) throws TclException { + return TclHashIdentifiedObject.createReflectObject(interp, DesignObject.class, this); + } + + public List getCmd() { + return cmd; + } + + private static boolean isSyntaxStr(UnsupportedConstraintElement elem, String s) { + if (!(elem instanceof UnsupportedConstraintElement.SyntaxConstraintElement)) { + return false; + } + return elem.toXdc().equals(s); + } + + public UnsupportedCmdResult withoutOutsideBrackets() { + if (isSyntaxStr(cmd.get(0), "[") && isSyntaxStr(cmd.get(cmd.size()-1), "]")) { + List newList = cmd.stream().skip(1).limit(cmd.size() - 2).collect(Collectors.toList()); + return new UnsupportedCmdResult<>(newList, lookup); + } + return new UnsupportedCmdResult<>(new ArrayList<>(cmd), lookup); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedGetterCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedGetterCommand.java new file mode 100644 index 000000000..4c4aad0fe --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedGetterCommand.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; + +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.ReflectObject; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclObject; +import tcl.lang.TclString; + +/** + * A setter command that is not supported in detail + */ +public class UnsupportedGetterCommand implements Command { + protected final EdifCellLookup lookup; + protected final Command replacedCommand; + + public UnsupportedGetterCommand(EdifCellLookup lookup, Command replacedCommand) { + this.lookup = lookup; + this.replacedCommand = replacedCommand; + } + public UnsupportedGetterCommand(EdifCellLookup lookup) { + this.lookup = lookup; + this.replacedCommand = null; + } + + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + if (replacedCommand!=null && Arrays.stream(objv).noneMatch(obj -> containsUnsupportedCmdResults(lookup, interp, obj, false))) { + replacedCommand.cmdProc(interp, objv); + } else { + interp.setResult(UnsupportedCmdResult.makeTclObj(interp, objv, lookup, true, true)); + } + } + + public static boolean containsUnsupportedCmdResults(EdifCellLookup lookup, Interp interp, TclObject obj, boolean isInList) { + try { + if (obj.getInternalRep() instanceof TclList) { + TclObject[] elements = TclList.getElements(interp, obj); + return Arrays.stream(elements).anyMatch(elem -> containsUnsupportedCmdResults(lookup, interp, elem, true)); + } else if (obj.getInternalRep() instanceof ReflectObject) { + Optional designObject = DesignObject.unwrapTclObject(interp, obj, lookup); + if (!designObject.isPresent()) { + return true; + } + return !isInList || !(designObject.get() instanceof CellObject); + } else if (obj.getInternalRep() instanceof TclString) { + return TclHashIdentifiedObject.containsStringifiedObject(obj.toString()); + } else { + return false; + } + } catch (TclException e) { + throw new RuntimeException(e); + } + } + + public static void replaceInInterp(Interp interp, EdifCellLookup lookup, String name) { + Command replacedCommand = Objects.requireNonNull(interp.getCommand(name)); + interp.createCommand(name, new UnsupportedGetterCommand(lookup, replacedCommand)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedIfCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedIfCommand.java new file mode 100644 index 000000000..632a32362 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedIfCommand.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.Objects; + +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.CharPointer; +import tcl.lang.Command; +import tcl.lang.ExprValue; +import tcl.lang.Interp; +import tcl.lang.Parser; +import tcl.lang.TCL; +import tcl.lang.TclDouble; +import tcl.lang.TclException; +import tcl.lang.TclInteger; +import tcl.lang.TclObject; +import tcl.lang.TclParse; +import tcl.lang.TclRuntimeError; +import tcl.lang.TclString; +import tcl.lang.TclToken; + +/** + * Replacement for if that supports the condition returning an UnsupportedConstraintElement + */ +public class UnsupportedIfCommand extends UnsupportedSetterCommand { + public UnsupportedIfCommand(XDCConstraints constraints, EdifCellLookup lookup, Command replacedCommand) { + super(constraints, lookup, replacedCommand); + } + + private TclObject exprValueToObj(ExprValue exprValue) { + switch (exprValue.getType()) { + case 0: + return TclInteger.newInstance(exprValue.getIntValue()); + case 1: + return TclDouble.newInstance(exprValue.getDoubleValue()); + case 2: + return TclString.newInstance("{"+exprValue.getStringValue()+"}"); + default: + throw new RuntimeException("invalid expr type"); + } + } + + //Copied from Parser.java + static final int TCL_TOKEN_WORD = 1; + static final int TCL_TOKEN_SIMPLE_WORD = 2; + static final int TCL_TOKEN_TEXT = 4; + static final int TCL_TOKEN_BS = 8; + static final int TCL_TOKEN_COMMAND = 16; + static final int TCL_TOKEN_VARIABLE = 32; + static final int TCL_TOKEN_SUB_EXPR = 64; + static final int TCL_TOKEN_OPERATOR = 128; + + String tokensToStr(TclParse tclParse, Interp interp) { + try { + StringBuilder res = new StringBuilder(); + boolean needsSpace = false; + for (int i = 0; i < tclParse.numTokens; i++) { + TclToken token = tclParse.tokenList[i]; + switch (token.type) { + case TCL_TOKEN_VARIABLE: + + String varName = tclParse.tokenList[++i].getTokenString(); + if (token.numComponents != 1) { + throw new RuntimeException("we don't currently support arrays here"); + } + TclObject value = interp.getVar(varName, null, 0); + if (needsSpace) { + res.append(' '); + } + res.append(value.toString()); + needsSpace = true; + break; + case TCL_TOKEN_SIMPLE_WORD: + case TCL_TOKEN_COMMAND: + if (needsSpace) { + res.append(' '); + } + res.append(token.getTokenString()); + needsSpace = true; + break; + case TCL_TOKEN_TEXT: + case TCL_TOKEN_WORD: + //Ignore + break; + default: + throw new RuntimeException("unsupported token type: "+token.type); + } + } + return res.toString(); + } catch (TclException e) { + throw new RuntimeException(e); + } + } + void makeUnsupportedResult(Interp interp, TclObject[] objv, int i) throws TclException { + + for (++i;i= objv.length) { + throw new TclException(interp, + "wrong # args: no expression after \"" + + objv[i - 1] + "\" argument"); + } + try { + ExprValue exprValue = interp.evalExpression(objv[i].toString()); + try { + value = exprValue.getBooleanValue(interp); + } catch (TclException | TclRuntimeError e) { + //Not evaluatable, forward if to Vivado + objv[i] = exprValueToObj(exprValue); + + makeUnsupportedResult(interp, objv, i); + return; + } finally { + interp.releaseExprValue(exprValue); + } + } catch (TclException e) { + switch (e.getCompletionCode()) { + case TCL.ERROR: + interp.addErrorInfo("\n (\"if\" test expression)"); + break; + } + throw e; + } + + i++; + if ((i < objv.length) && (objv[i].toString().equals("then"))) { + i++; + } + if (i >= objv.length) { + throw new TclException(interp, + "wrong # args: no script following \"" + + objv[i - 1] + "\" argument"); + } + if (value) { + try { + interp.eval(objv[i], 0); + } catch (TclException e) { + switch (e.getCompletionCode()) { + case TCL.ERROR: + interp.addErrorInfo("\n (\"if\" then script line " + + interp.getErrorLine() + ")"); + break; + } + throw e; + } + return; + } + + // The expression evaluated to false. Skip the command, then + // see if there is an "else" or "elseif" clause. + + i++; + if (i >= objv.length) { + interp.resetResult(); + return; + } + if (objv[i].toString().equals("elseif")) { + i++; + continue; + } + break; + } + + // Couldn't find a "then" or "elseif" clause to execute. + // Check now for an "else" clause. We know that there's at + // least one more argument when we get here. + + if (objv[i].toString().equals("else")) { + i++; + if (i >= objv.length) { + throw new TclException(interp, + "wrong # args: no script following \"else\" argument"); + } else if (i != (objv.length - 1)) { + throw new TclException(interp, + "wrong # args: extra words after \"else\" clause in " + + "\"if\" command"); + } + } else { + // Not else, if there is more than 1 more argument + // then generate an error. + + if (i != (objv.length - 1)) { + throw new TclException(interp, + "wrong # args: extra words after \"else\" clause in \"if\" command"); + } + } + try { + interp.eval(objv[i], 0); + } catch (TclException e) { + switch (e.getCompletionCode()) { + case TCL.ERROR: + interp.addErrorInfo("\n (\"if\" else script line " + + interp.getErrorLine() + ")"); + break; + } + throw e; + } + } + + public static void replaceInInterp(Interp interp, XDCConstraints xdcConstraints, EdifCellLookup lookup) { + Command replacedCommand = Objects.requireNonNull(interp.getCommand("if")); + interp.createCommand("if", new UnsupportedIfCommand(xdcConstraints, lookup, replacedCommand)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedSetterCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedSetterCommand.java new file mode 100644 index 000000000..3c5cdecd1 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedSetterCommand.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * A setter command that is not supported in detail + */ +public class UnsupportedSetterCommand implements Command { + + protected final XDCConstraints constraints; + protected final EdifCellLookup cellLookup; + protected final Command replacedCommand; + + public UnsupportedSetterCommand(XDCConstraints constraints, EdifCellLookup cellLookup, Command replacedCommand) { + + this.constraints = constraints; + this.cellLookup = cellLookup; + this.replacedCommand = replacedCommand; + } + + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + if (replacedCommand!=null && Arrays.stream(objv).noneMatch(obj -> UnsupportedGetterCommand.containsUnsupportedCmdResults(cellLookup, interp, obj, false))) { + replacedCommand.cmdProc(interp, objv); + } else { + List constraint = UnsupportedConstraintElement.commandToUnsupportedConstraints(interp, objv, cellLookup); + constraints.getUnsupportedConstraints().add(constraint); + + interp.resetResult(); + } + } + + public static void replaceInInterp(Interp interp, XDCConstraints constraints, EdifCellLookup lookup, String name) { + Command replacedCommand = Objects.requireNonNull(interp.getCommand(name)); + interp.createCommand(name, new UnsupportedSetterCommand(constraints, lookup, replacedCommand)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/XDCTools.java b/src/com/xilinx/rapidwright/design/xdc/parser/XDCTools.java new file mode 100644 index 000000000..b3b5bb19a --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/XDCTools.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc.parser; + +public class XDCTools { + public static boolean stringNeedsBraces(String s) { + return s.contains(" ") || s.contains("*") || s.contains("$"); + } + public static String braceEnclosedIfNeeded(String s) { + if (stringNeedsBraces(s)) { + return '{'+s+'}'; + } + return s; + } +} diff --git a/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java b/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java index a187b2f42..0048c5180 100644 --- a/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java +++ b/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java @@ -29,7 +29,7 @@ import com.xilinx.rapidwright.design.ConstraintGroup; import com.xilinx.rapidwright.design.Design; -import com.xilinx.rapidwright.ipi.XDCParser; +import com.xilinx.rapidwright.design.xdc.XDCParser; import com.xilinx.rapidwright.util.FileTools; public class DcpToInterchange { diff --git a/src/com/xilinx/rapidwright/ipi/BlockStitcher.java b/src/com/xilinx/rapidwright/ipi/BlockStitcher.java index 0b7de6d52..e3a4616e4 100644 --- a/src/com/xilinx/rapidwright/ipi/BlockStitcher.java +++ b/src/com/xilinx/rapidwright/ipi/BlockStitcher.java @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; +import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.Set; @@ -50,6 +51,8 @@ import com.xilinx.rapidwright.design.blocks.BlockInst; import com.xilinx.rapidwright.design.blocks.ImplGuide; import com.xilinx.rapidwright.design.blocks.PBlock; +import com.xilinx.rapidwright.design.xdc.PackagePinConstraint; +import com.xilinx.rapidwright.design.xdc.XDCParser; import com.xilinx.rapidwright.device.PartNameTools; import com.xilinx.rapidwright.device.Site; import com.xilinx.rapidwright.edif.EDIFCell; @@ -137,7 +140,7 @@ private Net validateNet(Net curr, Net found) { * @param design * @param constraints */ - public void stitchDesign(Design design, HashMap constraints) { + public void stitchDesign(Design design, Map constraints) { boolean debug = false; EDIFNetlist n = design.getNetlist(); // Create a reverse parent net map (Parent Net -> Children Nets: all logical nets that are physically equivalent) @@ -271,7 +274,7 @@ else if (net.getType() == NetType.VCC) { Net portNet = null; portNet = e.getValue(); - Site site = design.getDevice().getSiteFromPackagePin(constraints.get(portName).getName()); + Site site = design.getDevice().getSiteFromPackagePin(constraints.get(portName).getPackagePin()); if (site == null) { MessageGenerator.briefMessage("WARNING: It appears that the I/O called " + portName + " is not assigned to a package pin!"); continue nextPort; @@ -294,7 +297,7 @@ else if (net.getType() == NetType.VCC) { } String ioStandard = constraints.get(portName).getIoStandard(); - String pkgPin = constraints.get(portName).getName(); + String pkgPin = constraints.get(portName).getPackagePin(); EDIFNet logNet = e.getKey().getPortInst().getNet(); design.createAndPlaceIOB(portName, isPortOutput ? PinType.OUT : PinType.IN, pkgPin, ioStandard, portNet, logNet); } @@ -562,9 +565,9 @@ public static void main(String[] args) { runtimes[2] = System.currentTimeMillis(); System.out.println("Total Blocks : " + totalBlocks); String xdcFileName = args[1].replace(".edf", ".xdc"); - HashMap constraints = null; + Map constraints = null; if (new File(xdcFileName).exists()) { - constraints = XDCParser.parseXDC(xdcFileName,stitched.getDevice()); + constraints = XDCParser.parseXDC(xdcFileName,stitched.getDevice()).getPinConstraints(); } else { MessageGenerator.briefError("WARNING: Could not find XDC file " + xdcFileName); } diff --git a/src/com/xilinx/rapidwright/ipi/PackagePinConstraint.java b/src/com/xilinx/rapidwright/ipi/PackagePinConstraint.java deleted file mode 100644 index 49bb2a681..000000000 --- a/src/com/xilinx/rapidwright/ipi/PackagePinConstraint.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * - * Copyright (c) 2018-2022, Xilinx, Inc. - * Copyright (c) 2022, Advanced Micro Devices, Inc. - * All rights reserved. - * - * Author: Chris Lavin, Xilinx Research Labs. - * - * 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.ipi; - -/** - * Annotates a package pin name with an IO standard - * Created on: Jan 25, 2018 - */ -public class PackagePinConstraint { - - private String name; - - private String ioStandard; - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * @return the ioStandard - */ - public String getIoStandard() { - return ioStandard; - } - - /** - * @param ioStandard the ioStandard to set - */ - public void setIOStandard(String ioStandard) { - this.ioStandard = ioStandard; - } - - public String toString() { - return name + ":" + ioStandard; - } -} diff --git a/src/com/xilinx/rapidwright/ipi/XDCParser.java b/src/com/xilinx/rapidwright/ipi/XDCParser.java deleted file mode 100644 index 626ca3b9b..000000000 --- a/src/com/xilinx/rapidwright/ipi/XDCParser.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2017-2022, Xilinx, Inc. - * Copyright (c) 2022, Advanced Micro Devices, Inc. - * All rights reserved. - * - * Author: Chris Lavin, Xilinx Research Labs. - * - * 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.ipi; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.List; - -import com.xilinx.rapidwright.device.Device; -import com.xilinx.rapidwright.util.FileTools; - - -/** - * Parses an XDC file for package constraints only. Does not - * perform full XDC parsing. - * - * Created on: Jul 27, 2015 - */ -public class XDCParser { - - private static boolean expect(String expected, String found, int lineNum, String line) { - if (!expected.equals(found)) { - throw new RuntimeException("\nERROR: While parsing line:\n '" + - line + "' (line number " + lineNum + ")\n" + " Expected: '" + - expected + "'\n Found: '" + found + "'\nStack Trace:"); - } - return true; - } - - /** - * Very rudimentary parsing to extract IO placements and IO standards. This - * does not support many Tcl constructs. - * @param fileName Name of the XDC file to parse - * @param dev The device associated with the design. - * @return A map of port names to package pin information. - */ - public static HashMap parseXDC(String fileName, Device dev) { - HashMap constraints = new HashMap<>(); - int lineNum = 1; - for (String line : FileTools.getLinesFromTextFile(fileName)) { - if (line.trim().startsWith("#")) continue; - if (line.contains("set_property") && line.contains("PACKAGE_PIN")) { - String[] parts = line.split("\\s+"); - expect("set_property", parts[0], lineNum, line); - expect("PACKAGE_PIN", parts[1], lineNum, line); - String pinLoc = parts[2]; - if (!dev.getActivePackage().getPackagePinMap().containsKey(pinLoc)) { - expect("", pinLoc,lineNum,line); - } - expect("[get_ports", parts[3], lineNum, line); - String key = parts[4].substring(0, parts[4].lastIndexOf(']')); - key = key.replace("}", ""); - key = key.replace("{", ""); - PackagePinConstraint pkgPin = constraints.get(key); - if (pkgPin == null) { - pkgPin = new PackagePinConstraint(); - constraints.put(key, pkgPin); - } - pkgPin.setName(pinLoc); - } else if (line.contains("set_property") && line.contains("IOSTANDARD")) { - String[] parts = line.split("\\s+"); - expect("set_property", parts[0], lineNum, line); - expect("IOSTANDARD", parts[1], lineNum, line); - String ioStandard = parts[2]; - expect("[get_ports", parts[3], lineNum, line); - String key = parts[4].substring(0, parts[4].lastIndexOf(']')); - key = key.replace("}", ""); - key = key.replace("{", ""); - PackagePinConstraint pkgPin = constraints.get(key); - if (pkgPin == null) { - pkgPin = new PackagePinConstraint(); - constraints.put(key, pkgPin); - } - pkgPin.setIOStandard(ioStandard); - } - lineNum++; - } - - - return constraints; - } - - public static void writeXDC(List constraints, OutputStream out) { - if (constraints == null) return; - try { - for (String s : constraints) { - out.write(s.getBytes()); - out.write('\n'); - } - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/test/RapidWrightDCP b/test/RapidWrightDCP index 72829b123..fce08f70c 160000 --- a/test/RapidWrightDCP +++ b/test/RapidWrightDCP @@ -1 +1 @@ -Subproject commit 72829b1235814d8ea3145f6c99a9eb531028b3a5 +Subproject commit fce08f70c35ed42d75408ba4f14fb502b47dbd49 diff --git a/test/src/com/xilinx/rapidwright/design/TestNetTools.java b/test/src/com/xilinx/rapidwright/design/TestNetTools.java index a1c639b72..a1469be9c 100644 --- a/test/src/com/xilinx/rapidwright/design/TestNetTools.java +++ b/test/src/com/xilinx/rapidwright/design/TestNetTools.java @@ -26,9 +26,11 @@ import java.util.List; import java.util.stream.Collectors; +import com.xilinx.rapidwright.device.ClockRegion; import com.xilinx.rapidwright.device.Device; import com.xilinx.rapidwright.device.Node; import com.xilinx.rapidwright.device.PIP; +import com.xilinx.rapidwright.edif.EDIFHierNet; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -39,7 +41,7 @@ public class TestNetTools { /** * Tests the method NetTools.isGlobalClock(Net net). - * For each DCP file, Vivado uses the TCL command + * For each DCP file, Vivado uses the Tcl command * get_nets -hier -parent_net -filter { TYPE == "GLOBAL_CLOCK" } * to retrieve all nets of type GLOBAL_CLOCK. * The method NetTools.isGlobalClock(Net net) returns true for these nets and false for others. @@ -246,4 +248,14 @@ public void testGetRouteTreesLoop() { " INT_X9Y235/BYPASS_W4 ( 4) INT_X9Y235/INT.INT_NODE_IMUX_39_INT_OUT1->>BYPASS_W4\n" + " }] INT_X9Y235/INODE_W_1_FT1 ( 0) INT_X9Y235/INT.BYPASS_W4->>INODE_W_1_FT1"); } + + @Test + public void testFindClockRootVRoute() { + Design design = RapidWrightDCP.loadDCP("microblazeAndILA_3pblocks.dcp"); + Net clkNet = design.getNet("base_mb_i/clk_wiz_1/inst/clk_out1"); + Assertions.assertNotNull(clkNet); + Assertions.assertTrue(clkNet.hasPIPs()); + ClockRegion clockRoot = NetTools.findClockRootVRoute(clkNet).getTile().getClockRegion(); + Assertions.assertEquals(clockRoot, design.getDevice().getClockRegion(1, 1)); + } } diff --git a/test/src/com/xilinx/rapidwright/design/xdc/NicerStringifyList.java b/test/src/com/xilinx/rapidwright/design/xdc/NicerStringifyList.java new file mode 100644 index 000000000..460d495aa --- /dev/null +++ b/test/src/com/xilinx/rapidwright/design/xdc/NicerStringifyList.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jetbrains.annotations.NotNull; + +public class NicerStringifyList implements List { + + @Override + public String toString() { + return list.stream().map(Object::toString).map(s -> s.replaceAll("] ", "] \" + \n \"")).collect(Collectors.joining("\",\n\"", "\"", "\"")); + } + + public int size() { + return list.size(); + } + + public boolean isEmpty() { + return list.isEmpty(); + } + + public boolean contains(Object o) { + return list.contains(o); + } + + public Iterator iterator() { + return list.iterator(); + } + + public Object[] toArray() { + return list.toArray(); + } + + public T1[] toArray(@NotNull T1[] a) { + return list.toArray(a); + } + + public boolean add(T t) { + return list.add(t); + } + + public boolean remove(Object o) { + return list.remove(o); + } + + public boolean containsAll(@NotNull Collection c) { + return list.containsAll(c); + } + + public boolean addAll(@NotNull Collection c) { + return list.addAll(c); + } + + public boolean addAll(int index, @NotNull Collection c) { + return list.addAll(index, c); + } + + public boolean removeAll(@NotNull Collection c) { + return list.removeAll(c); + } + + public boolean retainAll(@NotNull Collection c) { + return list.retainAll(c); + } + + public void replaceAll(UnaryOperator operator) { + list.replaceAll(operator); + } + + public void sort(Comparator c) { + list.sort(c); + } + + public void clear() { + list.clear(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NicerStringifyList that = (NicerStringifyList) o; + return Objects.equals(list, that.list); + } + + @Override + public int hashCode() { + return list.hashCode(); + } + + public T get(int index) { + return list.get(index); + } + + public T set(int index, T element) { + return list.set(index, element); + } + + public void add(int index, T element) { + list.add(index, element); + } + + public T remove(int index) { + return list.remove(index); + } + + public int indexOf(Object o) { + return list.indexOf(o); + } + + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + public ListIterator listIterator() { + return list.listIterator(); + } + + public ListIterator listIterator(int index) { + return list.listIterator(index); + } + + public List subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } + + public Spliterator spliterator() { + return list.spliterator(); + } + + public boolean removeIf(Predicate filter) { + return list.removeIf(filter); + } + + public Stream stream() { + return list.stream(); + } + + public Stream parallelStream() { + return list.parallelStream(); + } + + public void forEach(Consumer action) { + list.forEach(action); + } + + private final List list; + + public NicerStringifyList(List list) { + this.list = list; + } +} diff --git a/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java b/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java index 1901b17ec..e8dba25bd 100644 --- a/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java +++ b/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java @@ -29,19 +29,63 @@ import org.junit.jupiter.api.Test; import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.ConstraintGroup; import com.xilinx.rapidwright.design.blocks.PBlock; +import com.xilinx.rapidwright.design.blocks.PblockProperty; import com.xilinx.rapidwright.support.RapidWrightDCP; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; public class TestConstraintTools { - @Test - public void testGetPBlockFromXDCConstraints() { - String dcpPath = RapidWrightDCP.getString("microblazeAndILA_3pblocks.dcp"); - Design d = Design.readCheckpoint(dcpPath); + @ParameterizedTest + @EnumSource(TestXDCParser.RoundtripMode.class) + public void testGetPBlockFromXDCConstraints(TestXDCParser.RoundtripMode roundtripMode) { + Design d = RapidWrightDCP.loadDCP("microblazeAndILA_3pblocks.dcp"); + d.getXDCConstraints(ConstraintGroup.LATE).add("set_property " + PblockProperty.IS_SOFT + " 1 [get_pblocks pblock_dbg_hub]"); + d.getXDCConstraints(ConstraintGroup.LATE).add("set_property " + PblockProperty.EXCLUDE_PLACEMENT + " 1 [get_pblocks pblock_u_ila_0]"); + roundtripMode.doRoundtrip(d); + Map pblockMap = ConstraintTools.getPBlockFromXDCConstraints(d); Assertions.assertEquals(3, pblockMap.size()); Assertions.assertTrue(pblockMap.containsKey("pblock_dbg_hub")); Assertions.assertTrue(pblockMap.containsKey("pblock_base_mb_i")); Assertions.assertTrue(pblockMap.containsKey("pblock_u_ila_0")); + + // Check for the property and cooresponding TclConstraints + String TclConstraints; + PBlock dbgHub = pblockMap.get("pblock_dbg_hub"); + Assertions.assertTrue(dbgHub.containRouting()); + Assertions.assertTrue(dbgHub.isSoft()); + Assertions.assertFalse(dbgHub.excludePlacement()); + TclConstraints = String.join(" ", dbgHub.getTclConstraints()); + Assertions.assertTrue( + TclConstraints.contains(PblockProperty.CONTAIN_ROUTING.toString()) + && TclConstraints.contains(PblockProperty.IS_SOFT.toString()) + && !TclConstraints.contains(PblockProperty.EXCLUDE_PLACEMENT.toString()) + ); + + PBlock baseMb = pblockMap.get("pblock_base_mb_i"); + Assertions.assertTrue(baseMb.containRouting()); + Assertions.assertFalse(baseMb.isSoft()); + Assertions.assertFalse(baseMb.excludePlacement()); + TclConstraints = String.join(" ", baseMb.getTclConstraints()); + Assertions.assertTrue( + TclConstraints.contains(PblockProperty.CONTAIN_ROUTING.toString()) + && !TclConstraints.contains(PblockProperty.IS_SOFT.toString()) + && !TclConstraints.contains(PblockProperty.EXCLUDE_PLACEMENT.toString()) + ); + + PBlock uila0 = pblockMap.get("pblock_u_ila_0"); + Assertions.assertTrue(uila0.containRouting()); + Assertions.assertFalse(uila0.isSoft()); + Assertions.assertTrue(uila0.excludePlacement()); + TclConstraints = String.join(" ", uila0.getTclConstraints()); + Assertions.assertTrue( + TclConstraints.contains(PblockProperty.CONTAIN_ROUTING.toString()) + && !TclConstraints.contains(PblockProperty.IS_SOFT.toString()) + && TclConstraints.contains(PblockProperty.EXCLUDE_PLACEMENT.toString()) + ); } } diff --git a/test/src/com/xilinx/rapidwright/design/xdc/TestXDCParser.java b/test/src/com/xilinx/rapidwright/design/xdc/TestXDCParser.java new file mode 100644 index 000000000..82fe4daa9 --- /dev/null +++ b/test/src/com/xilinx/rapidwright/design/xdc/TestXDCParser.java @@ -0,0 +1,1071 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * 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.design.xdc; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +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 java.util.WeakHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.ConstraintGroup; +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.xdc.parser.CellObject; +import com.xilinx.rapidwright.design.xdc.parser.DesignObject; +import com.xilinx.rapidwright.design.xdc.parser.EdifCellLookup; +import com.xilinx.rapidwright.design.xdc.parser.RegularEdifCellLookup; +import com.xilinx.rapidwright.design.xdc.parser.TclHashIdentifiedObject; +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.support.RapidWrightDCP; +import com.xilinx.rapidwright.util.FileTools; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import tcl.lang.Interp; +import tcl.lang.TCL; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +public class TestXDCParser { + + //Shared between all test class instances, but weak reference + private static WeakReference ethernetDesignShared = new WeakReference<>(null); + //Strong reference in our test class instance + private Design ethernetDesign; + + private Design getEthernetDesign() { + if (ethernetDesign == null) { + Design fromShared = ethernetDesignShared.get(); + if (fromShared != null) { + ethernetDesign = fromShared; + } else { + ethernetDesign = RapidWrightDCP.loadDCP("verilog_ethernet.dcp", true); + //ethernetDesignShared = new WeakReference<>(ethernetDesign); + } + } + return ethernetDesign; + } + + + public static Stream getSimpleArgs() { + Stream list = Stream.of( + Arguments.of( + (Function>) RegularEdifCellLookup::new, + Arrays.asList( + "set_property IDELAY_VALUE 0 [get_cells {phy_rx_ctl_idelay}]", + "set_property IDELAY_VALUE 0 [get_cells {phy_rxd_idelay_0}]", + "set_property IDELAY_VALUE 0 [get_cells {phy_rxd_idelay_1}]", + "set_property IDELAY_VALUE 0 [get_cells {phy_rxd_idelay_2}]", + "set_property IDELAY_VALUE 0 [get_cells {phy_rxd_idelay_3}]", + "set_property PACKAGE_PIN AA14 [get_ports phy_tx_clk]", + "set_property IOSTANDARD LVCMOS25 [get_ports phy_tx_clk]", + "set_property SLEW FAST [get_ports phy_tx_clk]", + "set_property DRIVE 16 [get_ports phy_tx_clk]", + "set_property DUMMY 0 [get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0]}]", + "set_input_delay 0 [get_ports uart_rxd]", + "set_false_path -to [get_ports {phy_reset_n test_asdf}]", + "set_output_delay 0 [get_ports phy_reset_n]" + ), + 5, + 6, + 1, + (Predicate) rm -> true + ), + Arguments.of( + (Function>) netlist -> null, + Arrays.asList( + "set_property IDELAY_VALUE 0 [get_cells {phy_rx_ctl_idelay}]", + "set_property IDELAY_VALUE 0 [get_cells {phy_rxd_idelay_*}]", + "set_property PACKAGE_PIN AA14 [get_ports phy_tx_clk]", + "set_property IOSTANDARD LVCMOS25 [get_ports phy_tx_clk]", + "set_property SLEW FAST [get_ports phy_tx_clk]", + "set_property DRIVE 16 [get_ports phy_tx_clk]", + "set_property DUMMY 0 [get_cells [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]/rd_ptr_reg_reg[0]]", + "set_input_delay 0 [get_ports uart_rxd]", + "set_false_path -to [get_ports {phy_reset_n test_asdf}]", + "set_output_delay 0 [get_ports phy_reset_n]" + ), + 6, + 2, + 1, + (Predicate) rm -> rm!=RoundtripMode.Roundtrip_with_netlist + ) + ); + return withRoundtripMode(list); + + } + + @MethodSource("getSimpleArgs") + @ParameterizedTest + public void testSimple(Function> lookupFactory, List expectedOutput, int unsupportedConstraints, int cellProperties, int pinConstraints, Predicate canHandleRoundtrip, RoundtripMode roundtripMode) { + Assumptions.assumeTrue(canHandleRoundtrip.test(roundtripMode)); + List orig = Arrays.asList( + "set_property IDELAY_VALUE 0 [get_cells {phy_rx_ctl_idelay phy_rxd_idelay_*}]", + "set_property -dict {LOC AA14 IOSTANDARD LVCMOS25 SLEW FAST DRIVE 16} [get_ports phy_tx_clk]", + "set_input_delay 0 [get_ports {uart_rxd}]", + "#comment", + "set_false_path -to [get_ports {phy_reset_n test_asdf}]", + "set_output_delay 0 [get_ports {phy_reset_n}]", + "set fifo_inst [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]", + "set_property DUMMY 0 [get_cells \"$fifo_inst/rd_ptr_reg_reg[0]\"]" + ); + List roundtripped = roundtripMode.doRoundtrip(getEthernetDesign().getNetlist(), orig); + XDCConstraints xdcConstraints = XDCParser.parseXDC(getEthernetDesign().getDevice(), roundtripped, lookupFactory.apply(getEthernetDesign().getNetlist())); + List actual = xdcConstraints.getAllAsXdc().sorted().collect(Collectors.toList()); + Collections.sort(expectedOutput); + Assertions.assertEquals(new NicerStringifyList<>(expectedOutput), new NicerStringifyList<>(actual)); + Assertions.assertEquals(unsupportedConstraints, xdcConstraints.getUnsupportedConstraints().size()); + Assertions.assertEquals(cellProperties, xdcConstraints.getCellProperties().size()); + Assertions.assertEquals(pinConstraints, xdcConstraints.getPinConstraints().size()); + } + + + @FunctionalInterface + interface TclObjConsumer { + void accept(Interp interp, TclObject obj, EDIFNetlist netlist, EdifCellLookup lookup) throws TclException; + } + + private void eval(RoundtripMode roundtripMode, String tcl, XDCConstraints constraints, TclObjConsumer downstream) { + String roundtripped = roundtripMode.doRoundtrip(getEthernetDesign().getNetlist(), Arrays.asList(tcl)).stream().collect(Collectors.joining("\n")); + eval(roundtripped, constraints, downstream); + } + + private void eval(String tcl, XDCConstraints constraints, TclObjConsumer downstream) { + EDIFNetlist netlist = getEthernetDesign().getNetlist(); + RegularEdifCellLookup cellLookup = new RegularEdifCellLookup(netlist); + eval(tcl, constraints, downstream, netlist, cellLookup); + } + + + private static void eval(String tcl, XDCConstraints constraints, TclObjConsumer downstream, EDIFNetlist netlist, EdifCellLookup cellLookup) { + Interp interp = XDCParser.makeTclInterp(constraints, netlist.getDevice(), cellLookup); + try { + interp.eval(tcl); + downstream.accept(interp, interp.getResult(), netlist, cellLookup); + } catch (TclException ex) { + int code = ex.getCompletionCode(); + switch (code) { + case TCL.ERROR: + throw new RuntimeException(interp.getResult().toString()+" in line "+interp.getErrorLine(), ex); + case TCL.BREAK: + throw new RuntimeException( + "invoked \"break\" outside of a loop", ex); + case TCL.CONTINUE: + throw new RuntimeException( + "invoked \"continue\" outside of a loop", ex); + default: + throw new RuntimeException( + "command returned bad error code: " + code, ex); + } + } finally { + interp.dispose(); + } + } + + private static Stream getComplexGetterArgs() { + return Stream.of( + Arguments.of( + "get_cells -hier -filter {(ORIG_REF_NAME == eth_mac_1g_rgmii || REF_NAME == eth_mac_1g_rgmii)}", + Arrays.asList("core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst") + ), + Arguments.of( + "set refname eth_mac_1g_rgmii\n get_cells -hier -filter \"(ORIG_REF_NAME == $refname || REF_NAME == $refname)\"", + Arrays.asList("core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst") + ), + Arguments.of( + "get_cells -hier -filter {(ORIG_REF_NAME == axis_async_fifo || REF_NAME == axis_async_fifo)}", + Arrays.asList("core_inst/eth_mac_inst/rx_fifo/fifo_inst","core_inst/eth_mac_inst/tx_fifo/fifo_inst") + ), + Arguments.of( + "get_cells -quiet -hier -regexp \".*/s_rst_sync\\[23\\]_reg_reg\" -filter \"PARENT == core_inst/eth_mac_inst/rx_fifo/fifo_inst\"", + Arrays.asList("core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync2_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync3_reg_reg") + ), + Arguments.of( + "set parent core_inst/eth_mac_inst/rx_fifo/fifo_inst\nget_cells -quiet -hier -regexp \".*/s_rst_sync\\[23\\]_reg_reg\" -filter \"PARENT == $parent\"", + Arrays.asList("core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync2_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync3_reg_reg") + ), + Arguments.of( + "set parent [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]\nget_cells -quiet -hier -regexp \".*/s_rst_sync\\[23\\]_reg_reg\" -filter \"PARENT == $parent\"", + Arrays.asList("core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync2_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync3_reg_reg") + ), + Arguments.of( + "get_cells -quiet -hier -regexp \".*/wr_ptr_update(_ack)?_sync\\[123\\]_reg_reg\" -filter \"PARENT == core_inst/eth_mac_inst/rx_fifo/fifo_inst\"", + Arrays.asList("core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_update_ack_sync1_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_update_ack_sync2_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_update_sync1_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_update_sync2_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_update_sync3_reg_reg") + ), + Arguments.of( + "set fifo_inst [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]\n" + + "get_cells \"$fifo_inst/rd_ptr_reg_reg[*] $fifo_inst/rd_ptr_gray_reg_reg[*]\"", + Arrays.asList( + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[0]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[10]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[11]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[12]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[1]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[2]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[4]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[5]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[6]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[7]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[8]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[9]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[10]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[11]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[12]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[1]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[2]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[3]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[4]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[5]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[6]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[7]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[8]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[9]" + ) + ), + Arguments.of( + "set fifo_inst [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]\n" + + "get_cells \"$fifo_inst/rd_ptr_reg_reg[2] $fifo_inst/rd_ptr_gray_reg_reg[3]\"", + Arrays.asList( + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[2]" + ) + ), + Arguments.of( + "get_cells [list]", + Collections.emptyList() + ), + Arguments.of( + "get_cells [list clk90_bufg_inst clk_200_bufg_inst]", + Arrays.asList( + "clk90_bufg_inst", + "clk_200_bufg_inst" + ) + ) + ); + } + + @ParameterizedTest + @MethodSource("getComplexGetterArgs") + public void testComplexGetters(String tcl, List expected) { + eval(tcl, null, (interp, res, netlist, cellLookup) -> { + DesignObject designObject = DesignObject.unwrapTclObject(interp, interp.getResult(), cellLookup).orElseThrow(()->new RuntimeException("expected design object")); + List actual = ((CellObject) designObject).getCells(); + List actualStr = actual.stream().map(EDIFHierCellInst::toString).sorted().collect(Collectors.toList()); + Assertions.assertEquals(expected,actualStr); + }); + } + + private static Stream withRoundtripMode(Stream args) { + return args.flatMap(arg-> Arrays.stream(RoundtripMode.values()).map(mode-> new Arguments() { + @Override + public Object[] get() { + Object[] objects = arg.get(); + Object[] res = new Object[objects.length+1]; + System.arraycopy(objects, 0, res, 0, objects.length); + res[objects.length] = mode; + return res; + } + })); + } + + + private static Stream getComplexSetterArgs() { + Stream args = Stream.of( + Arguments.of( + "set if_inst core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst\n" + + "set src_clk [get_clocks -of_objects [get_pins $if_inst/rgmii_tx_clk_1_reg/C]]\n" + + "set_max_delay -from [get_cells $if_inst/rgmii_tx_clk_1_reg] -to " + + "[get_cells $if_inst/clk_oddr_inst/oddr[0].oddr_inst] -datapath_only " + + "[expr [get_property -min PERIOD $src_clk]/4]", + Arrays.asList( + "set_max_delay -from [get_cells core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rgmii_tx_clk_1_reg]" + + " -to [get_cells {core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/clk_oddr_inst/oddr[0].oddr_inst}]" + + " -datapath_only [expr [get_property -min PERIOD " + + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rgmii_tx_clk_1_reg/C]]]/4]" + ) + ), + Arguments.of( + "set_max_delay [expr [get_property asdf]/[get_property asdf]]", + Arrays.asList("set_max_delay [expr [get_property asdf]/[get_property asdf]]") + ), + Arguments.of( + "set inst [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]\nset_max_delay [get_pins $inst/x/y]", + Arrays.asList("set_max_delay [get_pins core_inst/eth_mac_inst/rx_fifo/fifo_inst/x/y]") + ), + Arguments.of( + "foreach fifo_inst [get_cells -hier -filter {(ORIG_REF_NAME == axis_async_fifo || REF_NAME == axis_async_fifo)}] {\n" + + "set pin [get_pins $fifo_inst/rd_ptr_reg_reg[0]/C]\n" + + "set_max_delay $pin\n" + + "}", + Arrays.asList( + "set_max_delay [get_pins core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0]/C]", + "set_max_delay [get_pins core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[0]/C]" + ) + ), + Arguments.of( + "set_max_delay -datapath_only -from [get_cells [list]] " + + "-to [get_cells [list]] " + + "8.000", + Arrays.asList( + "set_max_delay -datapath_only -from [get_cells [list]] " + + "-to [get_cells [list]] " + + "8.000") + ) + ); + return withRoundtripMode(args); + + } + + @ParameterizedTest + @MethodSource("getComplexSetterArgs") + public void testComplexSetters(String tcl, List expected, RoundtripMode roundtripMode) { + XDCConstraints constraints = new XDCConstraints(); + eval(roundtripMode, tcl, constraints, (interp, res, netlist, cellLookup) -> { + List actual = constraints.getAllAsXdc().collect(Collectors.toList()); + Assertions.assertEquals(new NicerStringifyList<>(expected), new NicerStringifyList<>(actual)); + }); + } + + + public enum RoundtripMode { + /** + * Use constraints directly + */ + Normal(null), + /** + * Parse constraints with netlist, stringify, then run testcase + */ + Roundtrip_with_netlist(RegularEdifCellLookup::new), + /** + * Parse constraints without netlist, stringify, then run testcase + */ + Roundtrip_without_netlist(n->null); + + private final Function> lookupFactory; + + + public EdifCellLookup getLookup(EDIFNetlist netlist) { + return lookupFactory.apply(netlist); + } + + RoundtripMode(Function> lookupFactory) { + this.lookupFactory = lookupFactory; + } + + public List doRoundtrip(EDIFNetlist netlist, List lines) { + if (lookupFactory == null) { + return lines; + } + XDCConstraints xdcConstraints = XDCParser.parseXDC(netlist.getDevice(), lines, lookupFactory.apply(netlist)); + List res = xdcConstraints.getAllAsXdc().collect(Collectors.toList()); + for (String line : res) { + Assertions.assertFalse(TclHashIdentifiedObject.containsStringifiedObject(line), ()->"Output line contains hash object: "+line); + } + return res; + } + + public void doRoundtrip(Design design) { + for (ConstraintGroup value : ConstraintGroup.values()) { + List constraints = design.getXDCConstraints(value); + if (constraints == null) { + continue; + } + List roundtripped = doRoundtrip(design.getNetlist(), constraints); + if (roundtripped != constraints) { + constraints.clear(); + constraints.addAll(roundtripped); + } + } + } + } + + + @ParameterizedTest + @EnumSource(RoundtripMode.class) + void compareToVivadoConstraints(RoundtripMode roundtripMode) throws IOException { + EDIFNetlist netlist = getEthernetDesign().getNetlist(); + String xdcFilename = RapidWrightDCP.getString("verilog_ethernet_source_constraints.xdc"); + List origConstraints = FileTools.getLinesFromTextFile(xdcFilename); + List afterRoundtrip = roundtripMode.doRoundtrip(netlist, origConstraints); + XDCConstraints xdcConstraints = XDCParser.parseXDC(netlist.getDevice(), afterRoundtrip, new RegularEdifCellLookup(netlist)); + + + Map actualReplacements = getReplacementsForActual(); + + List actual = expandReplacements( + xdcConstraints.getAllAsXdc() + .filter(l->!l.contains("PACKAGE_PIN") && !l.contains("IOSTANDARD") &&!l.startsWith("set_property")), + actualReplacements); + List reference = expandReferenceConstraints(); + Assertions.assertEquals(new NicerStringifyList<>(reference), new NicerStringifyList<>(actual)); + + } + + /** + * This returns string replacements to clean up some formatting differences and replace the output of unsupported + * commands by hardcoding their result as returned by running them manually in Vivado's Tcl console + * @return replacers + */ + private static @NotNull Map getReplacementsForActual() { + Map actualReplacements = new HashMap<>(); + actualReplacements.put( + "set_input_delay 0 ", + "set_input_delay 0.0 " + ); + actualReplacements.put( + "set_output_delay 0 ", + "set_output_delay 0.0 " + ); + actualReplacements.put( + "set_max_delay -from", + "set_max_delay -datapath_only -from" + ); + actualReplacements.put( + "[get_property -min PERIOD clk_mmcm_out]", + "8.0" + ); + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0]/C]]", + "clk_mmcm_out" + ); + + + actualReplacements.put( + "[get_property -min PERIOD phy_rx_clk]", + "8.0" + ); + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[0]/C]]", + "phy_rx_clk" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[0]/C]]", + "clk_mmcm_out" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[0]/C]]", + "clk_mmcm_out" + ); + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/mii_select_reg_reg/C]]", + "clk_mmcm_out" + ); + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rx_prescale_reg[2]/C]]", + "phy_rx_clk" + ); + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rgmii_tx_clk_1_reg/C]]", + "clk_mmcm_out" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_cells {" + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[9]}]]", + "clk_mmcm_out" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[9]}]]", + "phy_rx_clk" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[9]}]]", + "clk_mmcm_out" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[9]}]]", + "clk_mmcm_out" + ); + + actualReplacements.put( + "[if {[llength clk_mmcm_out]} {get_property -min PERIOD clk_mmcm_out} {expr 1.0}]", + "8.0" + ); + actualReplacements.put( + "[if {[llength phy_rx_clk]} {get_property -min PERIOD phy_rx_clk} {expr 1.0}]", + "8.0" + ); + + + actualReplacements.put( + "[expr {8.0/4}]", + "2.0" + ); + actualReplacements.put( + "[expr 8.0/4]", + "2.0" + ); + + + actualReplacements.put( + "] -datapath_only", + "]" + ); + + actualReplacements.put(".000", ".0"); + return actualReplacements; + } + + + String streamExpand(String line, String from, String to, boolean[] changed) { + int i = line.indexOf(from); + if (i==-1) { + return line; + } + String before = line.substring(0, i); + String after = line.substring(i+from.length()); + if (to==null) { + if (!before.isEmpty() || !after.isEmpty()) { + throw new RuntimeException("tried to delete partial line "+from+" in "+line); + } + return null; + } + + changed[0] = true; + + return before+to+after; + } + + List expandReferenceConstraints() throws IOException { + + //Vivado leaves some wildcards in the stored constraints. Replace them with the list of matches + Map expands = new HashMap<>(); + expands.put("get_cells -hier -regexp {.*/(rx|tx)_rst_reg_reg\\[\\d\\]} -filter {PARENT == core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst}", + "get_cells {core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rx_rst_reg_reg[0] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rx_rst_reg_reg[1] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rx_rst_reg_reg[2] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rx_rst_reg_reg[3] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/tx_rst_reg_reg[0] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/tx_rst_reg_reg[1] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/tx_rst_reg_reg[2] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/tx_rst_reg_reg[3]}" + ); + expands.put( + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[*]}", + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[9]}" + ); + expands.put( + "get_cells {{core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[*]} {core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[*]}}", + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[9]}" + ); + expands.put( + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[*]}", + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[9]}" + ); + expands.put( + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[*]}", + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[9]}" + ); + expands.put( + "get_cells {{core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[*]} " + + "{core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[*]}}", + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[9]}" + ); + expands.put( + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[*]}", + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[9]}" + ); + expands.put( + "get_cells -quiet {{core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[*]} " + + "{core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[*]} " + + "{core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[*]}}", + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[9]}" + ); + expands.put( + "get_cells -quiet {{core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[*]} " + + "{core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[*]} " + + "{core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[*]}}", + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[9]}" + ); + expands.put( + "get_cells -quiet -hier -regexp {.*/sync_reg_reg\\[\\d+\\]} -filter {PARENT == sync_reset_inst}", + "get_cells {sync_reset_inst/sync_reg_reg[0] sync_reset_inst/sync_reg_reg[1] sync_reset_inst/sync_reg_reg[2] sync_reset_inst/sync_reg_reg[3]}" + ); + expands.put(".000", ".0"); + + List lines = getEthernetDesign().getXDCConstraints(ConstraintGroup.NORMAL); + return expandReplacements(lines.stream().filter(l->{ + String trimmed = l.trim(); + return !trimmed.isEmpty() && !trimmed.startsWith("#") && !l.contains("PACKAGE_PIN") && !l.contains("IOSTANDARD") &&!l.startsWith("set_property"); + }), expands); + } + + /** + * Repeatedly apply a list of String replacements on all lines until a stable point is reached + * @param lines the lines to modify + * @param expands replacements as map of from-to-pairs + * @return lines with replacements applied + */ + @NotNull + private List expandReplacements(Stream lines, Map expands) { + + return lines.flatMap(line -> { + + boolean[] changes = new boolean[]{false}; + int iterations = 0; + do { + iterations++; + if (iterations>10000) { + Assertions.fail("Too many iterations"); + } + changes[0] = false; + for (Map.Entry entry : expands.entrySet()) { + line = streamExpand(line, entry.getKey(), entry.getValue(), changes); + if (line == null) { + return Stream.empty(); + } + } + + } while (changes[0]); + return Stream.of(line); + }) + .sorted() + .collect(Collectors.toList()); + } + + private static List findAllDcp(Path dir) { + + try (Stream list = Files.list(dir)) { + return list.flatMap(path -> { + if (Files.isDirectory(path)) { + return findAllDcp(path).stream(); + } + if (path.toString().endsWith(".dcp")) { + return Stream.of(path); + } + return Stream.empty(); + }).collect(Collectors.toList()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static class DesignReference { + public final Path path; + private WeakReference design; + + public DesignReference(Path path) { + this.path = path; + } + + public Design getDesign() { + Design d = design == null ? null : design.get(); + if (d == null) { + d = Design.readCheckpoint(path, true); + this.design = new WeakReference<>(d); + } + return d; + } + + public String toString() { + return path.toString(); + } + + + } + + public static Stream getAllTheDesigns() { + Set skippedDcps = new HashSet<>(); + skippedDcps.add("picoblaze_ooc_X10Y235_unreadable_edif.dcp"); //Needs Vivado + + List modes = Arrays.asList( + RoundtripMode.Roundtrip_without_netlist, + RoundtripMode.Roundtrip_with_netlist + ); + + return findAllDcp(RapidWrightDCP.dirPath).stream() + .filter(p->!skippedDcps.contains(p.getFileName().toString())) + .flatMap(path -> { + DesignReference r = new DesignReference(path); + return modes.stream().map(factory -> Arguments.of(r, factory)); + }); + } + + HashMap> designCache = new HashMap<>(); + + @ParameterizedTest + @MethodSource("getAllTheDesigns") + void checkAllTheDesigns(DesignReference p, RoundtripMode mode) { + Design design = p.getDesign(); + EDIFNetlist netlist = design.getNetlist(); + + for (ConstraintGroup cg : ConstraintGroup.values()) { + List sourceText = design.getXDCConstraints(cg); + EdifCellLookup lookup = mode.getLookup(netlist); + XDCConstraints xdcConstraints = XDCParser.parseXDC(design.getDevice(), sourceText, lookup); + List stringified = xdcConstraints.getAllAsXdc().collect(Collectors.toList()); + XDCConstraints reparse = XDCParser.parseXDC(design.getDevice(), stringified, lookup); + Assertions.assertEquals(new NicerStringifyList<>(stringified), new NicerStringifyList<>(reparse.getAllAsXdc().collect(Collectors.toList()))); + } + + } +} From a4c2abaf710ecc3e51c0a767ceb05175ffe01e91 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Mon, 10 Nov 2025 20:44:34 -0700 Subject: [PATCH 21/24] Remove unnecessary methods, refactor name. Signed-off-by: Chris Lavin --- .../design/xdc/ConstraintTools.java | 18 ++---------------- .../design/xdc/TestConstraintTools.java | 2 +- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java b/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java index 075c0eef4..bf28ab91c 100644 --- a/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java +++ b/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java @@ -24,14 +24,10 @@ import java.util.HashMap; import java.util.Map; -import java.util.EnumSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import com.xilinx.rapidwright.design.ConstraintGroup; import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.blocks.PBlock; -import com.xilinx.rapidwright.design.blocks.PblockProperty; -import com.xilinx.rapidwright.design.ConstraintGroup; import com.xilinx.rapidwright.design.xdc.parser.RegularEdifCellLookup; /** @@ -41,7 +37,7 @@ */ public class ConstraintTools { - public static Map getPBlockFromXDCConstraints(Design d) { + public static Map getPBlocksFromXDCConstraints(Design d) { Map pblockMap = new HashMap<>(); for (ConstraintGroup cg : ConstraintGroup.values()) { @@ -53,14 +49,4 @@ public static Map getPBlockFromXDCConstraints(Design d) { return pblockMap; } - - private static String extractPBlockName(String line) { - Matcher m = PBLOCK_NAME_PATTERN.matcher(line); - return m.find() ? m.group(1) : null; - } - - private static String extractRange(String line) { - Matcher m = RANGE_PATTERN.matcher(line); - return m.find() ? m.group(1) : null; - } } diff --git a/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java b/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java index e8dba25bd..648a93c5b 100644 --- a/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java +++ b/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java @@ -47,7 +47,7 @@ public void testGetPBlockFromXDCConstraints(TestXDCParser.RoundtripMode roundtri d.getXDCConstraints(ConstraintGroup.LATE).add("set_property " + PblockProperty.EXCLUDE_PLACEMENT + " 1 [get_pblocks pblock_u_ila_0]"); roundtripMode.doRoundtrip(d); - Map pblockMap = ConstraintTools.getPBlockFromXDCConstraints(d); + Map pblockMap = ConstraintTools.getPBlocksFromXDCConstraints(d); Assertions.assertEquals(3, pblockMap.size()); Assertions.assertTrue(pblockMap.containsKey("pblock_dbg_hub")); Assertions.assertTrue(pblockMap.containsKey("pblock_base_mb_i")); From 0213db427d7ba5b49dfcd1faa03bfa379692459c Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 11 Nov 2025 08:10:27 -0700 Subject: [PATCH 22/24] Refactor Signed-off-by: Chris Lavin --- src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java | 2 +- .../com/xilinx/rapidwright/design/xdc/TestConstraintTools.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java b/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java index bf28ab91c..a57dfef0b 100644 --- a/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java +++ b/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java @@ -37,7 +37,7 @@ */ public class ConstraintTools { - public static Map getPBlocksFromXDCConstraints(Design d) { + public static Map getPBlocksFromXDC(Design d) { Map pblockMap = new HashMap<>(); for (ConstraintGroup cg : ConstraintGroup.values()) { diff --git a/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java b/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java index 648a93c5b..898a5bda0 100644 --- a/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java +++ b/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java @@ -47,7 +47,7 @@ public void testGetPBlockFromXDCConstraints(TestXDCParser.RoundtripMode roundtri d.getXDCConstraints(ConstraintGroup.LATE).add("set_property " + PblockProperty.EXCLUDE_PLACEMENT + " 1 [get_pblocks pblock_u_ila_0]"); roundtripMode.doRoundtrip(d); - Map pblockMap = ConstraintTools.getPBlocksFromXDCConstraints(d); + Map pblockMap = ConstraintTools.getPBlocksFromXDC(d); Assertions.assertEquals(3, pblockMap.size()); Assertions.assertTrue(pblockMap.containsKey("pblock_dbg_hub")); Assertions.assertTrue(pblockMap.containsKey("pblock_base_mb_i")); From 2a19bab753fc519b1aa3e9f06c0250d2ea9c60ed Mon Sep 17 00:00:00 2001 From: Andrew Butt Date: Tue, 11 Nov 2025 13:54:56 -0700 Subject: [PATCH 23/24] Only remove top level Vivado bus prevention annotations (#1317) * Only remove top level Vivado bus prevention annotations Signed-off-by: Andrew Butt * Update src/com/xilinx/rapidwright/edif/EDIFCell.java Co-authored-by: Chris Lavin Signed-off-by: Andrew Butt * Update javadoc Signed-off-by: Andrew Butt --------- Signed-off-by: Andrew Butt Signed-off-by: Andrew Butt Co-authored-by: Chris Lavin --- src/com/xilinx/rapidwright/edif/EDIFCell.java | 12 ++++++--- .../xilinx/rapidwright/edif/EDIFTools.java | 25 ++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/com/xilinx/rapidwright/edif/EDIFCell.java b/src/com/xilinx/rapidwright/edif/EDIFCell.java index ed0696fc8..524e34090 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFCell.java +++ b/src/com/xilinx/rapidwright/edif/EDIFCell.java @@ -313,7 +313,7 @@ public EDIFPort getPort(String name) { /** * Given a port instance name (not including the name of the cell instance), * gets the associated port. - * + * * @param portInstName * @return */ @@ -809,8 +809,10 @@ public boolean isUniquified() { } /** - * Checks if this cell and the provided cell have the same set of ports - * + * Checks if this cell and the provided cell have the same set of ports. + * Port names that are the same except for starting with EDIFTools.VIVADO_PRESERVE_PORT_INTERFACE ("[]") are + * considered equivalent for the purpose of cells having a matching set of ports. + * * @param other The other cell to match against. * @return True if the set of ports on both this cell and the other cell match * exactly. False otherwise. @@ -822,6 +824,10 @@ public boolean matchesInterface(EDIFCell other) { Map otherPorts = other.getPortMap(); for (EDIFPort port : getPorts()) { EDIFPort otherPort = otherPorts.get(port.getBusName(true)); + if (otherPort == null) { + otherPort = otherPorts.get(EDIFTools.VIVADO_PRESERVE_PORT_INTERFACE + + port.getBusName(true)); + } if (otherPort == null || port.getWidth() != otherPort.getWidth() || port.getDirection() != otherPort.getDirection()) { return false; diff --git a/src/com/xilinx/rapidwright/edif/EDIFTools.java b/src/com/xilinx/rapidwright/edif/EDIFTools.java index 3ffba6c37..286fb70cf 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFTools.java +++ b/src/com/xilinx/rapidwright/edif/EDIFTools.java @@ -1783,18 +1783,25 @@ public static void ensurePreservedInterfaceVivado(EDIFNetlist netlist) { } } + /** + * Removes vivado bus prevention annotations from top-level ports. Vivado sometimes adds these annotations + * to prevent multiple single bit ports with similar names from getting merged into a single bus. The method + * used here will only work on the top-level cell as we do not traverse the netlist to ensure the new names + * are consistent. + * + * @param netlist The netlist to remove bus prevention annotations from. + * + */ public static void removeVivadoBusPreventionAnnotations(EDIFNetlist netlist) { EDIFCell top = netlist.getTopCell(); - for (EDIFCell cell : netlist.getLibrary(top.getLibrary().getName()).getCells()) { - List portsToRename = new ArrayList<>(); - for (EDIFPort p : cell.getPorts()) { - if (p.getName().startsWith(VIVADO_PRESERVE_PORT_INTERFACE)) { - portsToRename.add(p.getName()); - } - } - for (String p : portsToRename) { - cell.renamePort(p, p.substring(VIVADO_PRESERVE_PORT_INTERFACE.length())); + List portsToRename = new ArrayList<>(); + for (EDIFPort p : top.getPorts()) { + if (p.getName().startsWith(VIVADO_PRESERVE_PORT_INTERFACE)) { + portsToRename.add(p.getName()); } } + for (String p : portsToRename) { + top.renamePort(p, p.substring(VIVADO_PRESERVE_PORT_INTERFACE.length())); + } } } From 8ba3a547819cef8fe68875405e38bdb6baee2caf Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 13 Nov 2025 15:12:02 -0700 Subject: [PATCH 24/24] [EDIFPort] Refactor getBitBlastedIndices() (#1321) * [EDIFPort] Refactor getBitBlastedIndices() Signed-off-by: Chris Lavin * Check if isBus() Signed-off-by: Chris Lavin * Update src/com/xilinx/rapidwright/edif/EDIFPort.java Co-authored-by: eddieh-xlnx Signed-off-by: Chris Lavin * Fixing chronic misspelling of indices Signed-off-by: Chris Lavin * Update src/com/xilinx/rapidwright/edif/EDIFPort.java Co-authored-by: eddieh-xlnx Signed-off-by: Chris Lavin --------- Signed-off-by: Chris Lavin Co-authored-by: eddieh-xlnx --- .../rapidwright/debug/DotEdifDumper.java | 2 +- .../design/tools/InlineFlopTools.java | 2 +- .../device/helper/TileColumnPattern.java | 2 +- src/com/xilinx/rapidwright/edif/EDIFCell.java | 3 +- src/com/xilinx/rapidwright/edif/EDIFPort.java | 34 +++++++++++++++---- .../xilinx/rapidwright/edif/EDIFTools.java | 6 ++-- .../interchange/DeviceResourcesVerifier.java | 14 ++++---- .../interchange/DeviceResourcesWriter.java | 16 ++++----- .../rapidwright/design/TestPartitionPin.java | 13 ++----- .../xilinx/rapidwright/edif/TestEDIFCell.java | 6 ++-- .../xilinx/rapidwright/edif/TestEDIFPort.java | 2 +- 11 files changed, 56 insertions(+), 44 deletions(-) diff --git a/src/com/xilinx/rapidwright/debug/DotEdifDumper.java b/src/com/xilinx/rapidwright/debug/DotEdifDumper.java index f17cc5da9..e6eadcbea 100755 --- a/src/com/xilinx/rapidwright/debug/DotEdifDumper.java +++ b/src/com/xilinx/rapidwright/debug/DotEdifDumper.java @@ -63,7 +63,7 @@ protected Stream getPorts(EDIFCellInst edifCellInst) { protected Stream> getPortTemplates(EDIFCellInst edifCellInst) { return edifCellInst.getCellType().getPorts().stream().flatMap(p-> { if (p.isBus()) { - return Arrays.stream(p.getBitBlastedIndicies()).mapToObj(i -> new Pair<>(p, i)); + return Arrays.stream(p.getBitBlastedIndices()).mapToObj(i -> new Pair<>(p, i)); } return Stream.of(new Pair<>(p, 0)); }); diff --git a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java index 7416ff84d..231dc65d1 100644 --- a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java +++ b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java @@ -98,7 +98,7 @@ public static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clk continue; } if (port.isBus()) { - for (int i : port.getBitBlastedIndicies()) { + for (int i : port.getBitBlastedIndices()) { EDIFPortInst inst = port.getInternalPortInstFromIndex(i); Pair loc = nextAvailPlacement(design, siteItr); Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); diff --git a/src/com/xilinx/rapidwright/device/helper/TileColumnPattern.java b/src/com/xilinx/rapidwright/device/helper/TileColumnPattern.java index e2e2bd2ec..eb99ebbff 100644 --- a/src/com/xilinx/rapidwright/device/helper/TileColumnPattern.java +++ b/src/com/xilinx/rapidwright/device/helper/TileColumnPattern.java @@ -86,7 +86,7 @@ public static TileColumnPattern createTileColumnPattern(List types /** * Creates a TileColumnPattern from an existing list of tile types and uses the start and end - * as indicies to get a sublist of filteredTypes. + * as indices to get a sublist of filteredTypes. * @param filteredTypes The list of tile types to turn into a pattern * @param start The start index to use to build a subList of filteredTypes * @param end The end index to use to build a subList of filteredTypes diff --git a/src/com/xilinx/rapidwright/edif/EDIFCell.java b/src/com/xilinx/rapidwright/edif/EDIFCell.java index 75fee845e..23ee50845 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFCell.java +++ b/src/com/xilinx/rapidwright/edif/EDIFCell.java @@ -346,9 +346,8 @@ public void renamePort(String currName, String newName) { EDIFPort newPort = new EDIFPort(newName, port.getDirection(), port.getWidth()); addPort(newPort); - int[] indices = port.isBus() ? port.getBitBlastedIndicies() : new int[]{0}; - for (int i : indices) { + for (int i : port.getBitBlastedIndices()) { EDIFPortInst portInst = port.getInternalPortInstFromIndex(i); if (portInst == null) { continue; diff --git a/src/com/xilinx/rapidwright/edif/EDIFPort.java b/src/com/xilinx/rapidwright/edif/EDIFPort.java index 1efe0b4a5..30e1707d0 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFPort.java +++ b/src/com/xilinx/rapidwright/edif/EDIFPort.java @@ -108,7 +108,7 @@ else if (c == '[') { } } if (colonIdx == -1 || leftBracket == -1) { - throw new RuntimeException("ERROR: Interpreting port " + getName() + ", couldn't identify indicies."); + throw new RuntimeException("ERROR: Interpreting port " + getName() + ", couldn't identify indices."); } int left = Integer.parseInt(name.substring(leftBracket+1, colonIdx)); @@ -356,13 +356,33 @@ public boolean isBus() { return width > 1 || !getName().equals(busName); } + private static final int[] SINGLE_BIT_INDICES = new int[] { 0 }; + + /** + * @see #getBitBlastedIndicies() + * @deprecated Misspelling in name, to be removed in 2026.1.0 + */ public int[] getBitBlastedIndicies() { - int lastLeftBracket = getName().lastIndexOf('['); - if (getName().contains(":")) - return EDIFTools.bitBlastBus(getName().substring(lastLeftBracket)); - if (getName().contains("[")) - return new int[] {Integer.parseInt(getName().substring(lastLeftBracket,getName().length()-1))}; - return null; + return getBitBlastedIndices(); + } + + /** + * Returns an array of all the integer indices of this port. If the port is a + * single bit it returns an array with a single entry of '0'. This is useful + * when needing to iterate over a port's PortInst objects. + * + * @return The integer list of indices of this port, or {0} for a single bit + * port. + */ + public int[] getBitBlastedIndices() { + if (isBus()) { + int lastLeftBracket = getName().lastIndexOf('['); + assert(lastLeftBracket != -1); + return getName().indexOf(':', lastLeftBracket) != -1 ? + EDIFTools.bitBlastBus(getName().substring(lastLeftBracket)) : + new int[] { Integer.parseInt(getName().substring(lastLeftBracket, getName().length() - 1)) }; + } + return SINGLE_BIT_INDICES; } public boolean isBusRangeEqual(EDIFPort otherPort) { diff --git a/src/com/xilinx/rapidwright/edif/EDIFTools.java b/src/com/xilinx/rapidwright/edif/EDIFTools.java index 3ffba6c37..6d8c6c55a 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFTools.java +++ b/src/com/xilinx/rapidwright/edif/EDIFTools.java @@ -475,7 +475,7 @@ else if (c == '[') { } } if (colonIdx == -1 || leftBracket == -1) { - throw new RuntimeException("ERROR: Interpreting port " + name + ", couldn't identify indicies."); + throw new RuntimeException("ERROR: Interpreting port " + name + ", couldn't identify indices."); } int left = Integer.parseInt(name.substring(leftBracket+1, colonIdx)); @@ -1736,7 +1736,7 @@ public static EDIFNetlist createFlatNetlist(EDIFNetlist netlist, String partName for (EDIFPort topPort : netlist.getTopCell().getPorts()) { EDIFPort flatPort = flatTop.createPort(topPort); if (flatPort.isBus()) { - int[] indicies = flatPort.getBitBlastedIndicies(); + int[] indices = flatPort.getBitBlastedIndices(); int i = 0; for (EDIFNet net : topPort.getInternalNets()) { if (net == null) continue; @@ -1744,7 +1744,7 @@ public static EDIFNetlist createFlatNetlist(EDIFNetlist netlist, String partName if (flatNet == null) { flatNet = flatTop.createNet(net.getName()); } - flatNet.createPortInst(flatPort, indicies[i++]); + flatNet.createPortInst(flatPort, indices[i++]); } } else { EDIFNet net = topPort.getInternalNet(); diff --git a/src/com/xilinx/rapidwright/interchange/DeviceResourcesVerifier.java b/src/com/xilinx/rapidwright/interchange/DeviceResourcesVerifier.java index 79af2a005..44ad316a6 100644 --- a/src/com/xilinx/rapidwright/interchange/DeviceResourcesVerifier.java +++ b/src/com/xilinx/rapidwright/interchange/DeviceResourcesVerifier.java @@ -189,7 +189,7 @@ public static boolean verifyDeviceResources(String devResFileName, String device Reader stReaders = dReader.getSiteTypeList(); for (SiteType.Reader stReader : stReaders) { - Set belPinIndicies = new HashSet(); + Set belPinIndices = new HashSet(); SiteTypeEnum siteTypeEnum = SiteTypeEnum.valueOf(allStrings.get(stReader.getName())); Site site = siteTypes.get(siteTypeEnum); @@ -212,7 +212,7 @@ public static boolean verifyDeviceResources(String devResFileName, String device for (int j=0; j < belPins.length; j++) { BELPin pin = belPins[j]; int belPinIndex = pinsReader.get(j); - belPinIndicies.add(belPinIndex); + belPinIndices.add(belPinIndex); verifyBelPin(belPinsReader, pin, belPinIndex); } @@ -223,11 +223,11 @@ public static boolean verifyDeviceResources(String devResFileName, String device BELInverter.Reader belInverter = belReader.getInverting(); BELPin nonInverting = bel.getNonInvertingPin(); - belPinIndicies.add(belInverter.getNonInvertingPin()); + belPinIndices.add(belInverter.getNonInvertingPin()); verifyBelPin(belPinsReader, nonInverting, belInverter.getNonInvertingPin()); BELPin inverting = bel.getInvertingPin(); - belPinIndicies.add(belInverter.getInvertingPin()); + belPinIndices.add(belInverter.getInvertingPin()); verifyBelPin(belPinsReader, inverting, belInverter.getInvertingPin()); } else { expect(false, belReader.hasInverting()); @@ -274,7 +274,7 @@ public static boolean verifyDeviceResources(String devResFileName, String device } BELPin belPin = belPins[0]; - belPinIndicies.add(pinReader.getBelpin()); + belPinIndices.add(pinReader.getBelpin()); verifyBelPin(belPinsReader, belPin, pinReader.getBelpin()); } @@ -297,12 +297,12 @@ public static boolean verifyDeviceResources(String devResFileName, String device expect(belPins.length, wiresReader.size()); for (int i=0; i < belPins.length; i++) { - belPinIndicies.add(wiresReader.get(i)); + belPinIndices.add(wiresReader.get(i)); verifyBelPin(belPinsReader, belPins[i], wiresReader.get(i)); } } - expect(belPinIndicies.size(), belPinsReader.size()); + expect(belPinIndices.size(), belPinsReader.size()); StructList.Reader sitePipsReader = stReader.getSitePIPs(); SitePIP[] sitePIPs = siteInst.getSitePIPs(); diff --git a/src/com/xilinx/rapidwright/interchange/DeviceResourcesWriter.java b/src/com/xilinx/rapidwright/interchange/DeviceResourcesWriter.java index 01a19e554..ac5c233d9 100644 --- a/src/com/xilinx/rapidwright/interchange/DeviceResourcesWriter.java +++ b/src/com/xilinx/rapidwright/interchange/DeviceResourcesWriter.java @@ -288,14 +288,14 @@ public static void writeDeviceResourcesFile(String part, Device device, CodePerf writeAllSiteTypesToBuilder(design, device, devBuilder); t.stop().start("TileTypes"); - Map tileTypeIndicies = writeAllTileTypesToBuilder(design, device, devBuilder); + Map tileTypeIndices = writeAllTileTypesToBuilder(design, device, devBuilder); Map tileTypesObj = new HashMap(); - for (Map.Entry tileType : tileTypeIndicies.entrySet()) { + for (Map.Entry tileType : tileTypeIndices.entrySet()) { tileTypesObj.put(tileType.getKey(), devBuilder.getTileTypeList().get(tileType.getValue())); } t.stop().start("Tiles"); - writeAllTilesToBuilder(device, devBuilder, tileTypeIndicies); + writeAllTilesToBuilder(device, devBuilder, tileTypeIndices); t.stop().start("Wires&Nodes"); writeAllWiresAndNodesToBuilder(device, devBuilder, skipRouteResources); @@ -696,7 +696,7 @@ private static void populateAltSitePins( public static Map writeAllTileTypesToBuilder(Design design, Device device, DeviceResources.Device.Builder devBuilder) { StructList.Builder tileTypesList = devBuilder.initTileTypeList(tileTypes.size()); - Map tileTypeIndicies = new HashMap(); + Map tileTypeIndices = new HashMap(); // Order tile types by their TILE_TYPE_IDX (may not be contiguous) Map tileTypeIndexMap = new TreeMap<>(); @@ -709,7 +709,7 @@ public static Map writeAllTileTypesToBuilder(Design desig TileTypeEnum type = e.getValue(); Tile tile = tileTypes.get(type); TileType.Builder tileType = tileTypesList.get(i); - tileTypeIndicies.put(type, i); + tileTypeIndices.put(type, i); // name tileType.setName(allStrings.getIndex(type.name())); @@ -792,10 +792,10 @@ public static Map writeAllTileTypesToBuilder(Design desig i++; } - return tileTypeIndicies; + return tileTypeIndices; } - public static void writeAllTilesToBuilder(Device device, DeviceResources.Device.Builder devBuilder, Map tileTypeIndicies) { + public static void writeAllTilesToBuilder(Device device, DeviceResources.Device.Builder devBuilder, Map tileTypeIndices) { StructList.Builder tileBuilders = devBuilder.initTileList(device.getColumns() * device.getRows()); @@ -804,7 +804,7 @@ public static void writeAllTilesToBuilder(Device device, DeviceResources.Device. for (Tile tile : tiles) { DeviceResources.Device.Tile.Builder tileBuilder = tileBuilders.get(i); tileBuilder.setName(allStrings.getIndex(tile.getName())); - tileBuilder.setType(tileTypeIndicies.get(tile.getTileTypeEnum())); + tileBuilder.setType(tileTypeIndices.get(tile.getTileTypeEnum())); Site[] sites = tile.getSites(); StructList.Builder siteBuilders = tileBuilder .initSites(sites.length); diff --git a/test/src/com/xilinx/rapidwright/design/TestPartitionPin.java b/test/src/com/xilinx/rapidwright/design/TestPartitionPin.java index a5485464b..16d9381b1 100644 --- a/test/src/com/xilinx/rapidwright/design/TestPartitionPin.java +++ b/test/src/com/xilinx/rapidwright/design/TestPartitionPin.java @@ -81,17 +81,10 @@ public void testPartitionPins() { int wireIdx = 10; int count = 0; for (EDIFPort port : design.getTopEDIFCell().getPorts()) { - if (port.isBus()) { - for (int i : port.getBitBlastedIndicies()) { - Node node = Node.getNode(t, wireIdx++); - PartitionPin ppin = design.createPartitionPin(port, i, node); - testPortPartitionPin(design, ppin, port, i, node); - count++; - } - } else { + for (int i : port.getBitBlastedIndices()) { Node node = Node.getNode(t, wireIdx++); - PartitionPin ppin = design.createPartitionPin(port, node); - testPortPartitionPin(design, ppin, port, -1, node); + PartitionPin ppin = design.createPartitionPin(port, i, node); + testPortPartitionPin(design, ppin, port, i, node); count++; } } diff --git a/test/src/com/xilinx/rapidwright/edif/TestEDIFCell.java b/test/src/com/xilinx/rapidwright/edif/TestEDIFCell.java index 066ae7c14..531c995ff 100644 --- a/test/src/com/xilinx/rapidwright/edif/TestEDIFCell.java +++ b/test/src/com/xilinx/rapidwright/edif/TestEDIFCell.java @@ -149,7 +149,7 @@ public void testRenamePort() { EDIFPort myNewSignal = topCell.getPort("my_new_signal["); Assertions.assertNotNull(myNewSignal); - for (int i : myNewSignal.getBitBlastedIndicies()) { + for (int i : myNewSignal.getBitBlastedIndices()) { EDIFPortInst portInst = myNewSignal.getInternalPortInstFromIndex(i); Assertions.assertNotNull(portInst); } @@ -164,7 +164,7 @@ public void testRenamePort() { EDIFPort newTestZero = topCell.getPort("new_test_zero["); Assertions.assertNotNull(newTestZero); - for (int i : newTestZero.getBitBlastedIndicies()) { + for (int i : newTestZero.getBitBlastedIndices()) { EDIFPortInst portInst = newTestZero.getInternalPortInstFromIndex(i); Assertions.assertNotNull(portInst); } @@ -179,7 +179,7 @@ public void testRenamePort() { EDIFPort newTestFive = topCell.getPort("new_test_five["); Assertions.assertNotNull(newTestFive); - for (int i : newTestFive.getBitBlastedIndicies()) { + for (int i : newTestFive.getBitBlastedIndices()) { EDIFPortInst portInst = newTestFive.getInternalPortInstFromIndex(i); Assertions.assertNotNull(portInst); } diff --git a/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java b/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java index 525359017..728cdc8f9 100644 --- a/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java +++ b/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java @@ -96,7 +96,7 @@ public void testCreatePort() { EDIFPort copy = cell.getPort("bus_output[" + outer + "]["); Assertions.assertEquals(busOutput, copy); - int[] portIndices = busOutput.getBitBlastedIndicies(); + int[] portIndices = busOutput.getBitBlastedIndices(); Assertions.assertEquals(width, portIndices.length); Assertions.assertEquals(left, portIndices[0]); Assertions.assertEquals(right, portIndices[portIndices.length - 1]);