From 80a5bdedd7c716a9df120a48c633e9a6161015a6 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Tue, 28 Apr 2026 16:33:04 -0600 Subject: [PATCH 1/7] Fix IBUF creation for UltraScale+ (#1357) * rc2 fix IBUF creation Signed-off-by: Chris Lavin * Add a test Signed-off-by: Chris Lavin * Resolve warning messages by checking logical netlist for top level ports Signed-off-by: Chris Lavin * -rc3 Signed-off-by: Chris Lavin --------- Signed-off-by: Chris Lavin --- .classpath | 4 +-- .github/workflows/build.yml | 2 +- src/com/xilinx/rapidwright/router/Router.java | 2 +- .../xilinx/rapidwright/design/TestDesign.java | 36 +++++++++++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/.classpath b/.classpath index 9f88d326f1..dfd397f529 100644 --- a/.classpath +++ b/.classpath @@ -33,9 +33,9 @@ - + - + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f155aacd9e..76bdde45c1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: pull_request: env: - RAPIDWRIGHT_VERSION: v2025.2.1-beta + RAPIDWRIGHT_VERSION: v2025.2.2-rc3-beta jobs: diff --git a/src/com/xilinx/rapidwright/router/Router.java b/src/com/xilinx/rapidwright/router/Router.java index e2ac71e178..cb79fc92ed 100644 --- a/src/com/xilinx/rapidwright/router/Router.java +++ b/src/com/xilinx/rapidwright/router/Router.java @@ -2006,7 +2006,7 @@ public Design routeDesign() { if (currNet.getSource() == null && !currNet.isStaticNet()) { EDIFNet logNet = currNet.getLogicalNet(); - if (logNet != null && logNet.getParentCell().getName().equals("IOBUF")) { + if (logNet != null && logNet.getSourcePortInsts(true).size() > 0) { continue; } if (!supressWarningsErrors) MessageGenerator.briefError("WARNING: " + currNet.getName() + " does not have a source pin associated with it."); diff --git a/test/src/com/xilinx/rapidwright/design/TestDesign.java b/test/src/com/xilinx/rapidwright/design/TestDesign.java index 1bb0dddd39..1f8e9f0b39 100644 --- a/test/src/com/xilinx/rapidwright/design/TestDesign.java +++ b/test/src/com/xilinx/rapidwright/design/TestDesign.java @@ -59,6 +59,7 @@ import com.xilinx.rapidwright.edif.EDIFNetlist; import com.xilinx.rapidwright.edif.EDIFPort; import com.xilinx.rapidwright.edif.EDIFTools; +import com.xilinx.rapidwright.router.Router; import com.xilinx.rapidwright.support.RapidWrightDCP; import com.xilinx.rapidwright.tests.CodePerfTracker; import com.xilinx.rapidwright.util.FileTools; @@ -524,6 +525,41 @@ public void testCreateModuleInstCopiesStaticSource(boolean unrouteStaticNets) { } } + @ParameterizedTest + @CsvSource({ + "xc7z020clg400-1,SLICE_X100Y100/A6LUT,D19,D20,R14,LVCMOS33", + "xcku040-ffva1156-2-e,SLICE_X32Y46/A6LUT,AE10,AF9,AP8,LVCMOS18", + "xcau15p-ffvb676-2-e,SLICE_X1Y1/A6LUT,U26,R21,R20,LVCMOS12", + }) + public void testCreateAndPlaceIOB(String partName, String lutLoc, String b0, String b1, String led, String ioStandard) { + Design d = new Design("HelloWorld", partName); + + // Create all the design elements (LUT2, and 3 IOs) + Cell and2 = d.createAndPlaceCell("and2", Unisim.AND2, lutLoc); + Cell button0 = d.createAndPlaceIOB("button0", PinType.IN, b0, ioStandard); + Cell button1 = d.createAndPlaceIOB("button1", PinType.IN, b1, ioStandard); + Cell led0 = d.createAndPlaceIOB("led0", PinType.OUT, led, ioStandard); + + // Connect Button 0 to the LUT2 input I0 + Net net0 = d.createNet("button0_IBUF"); + net0.connect(button0, "O"); + net0.connect(and2, "I0"); + + // Connect Button 1 to the LUT2 input I1 + Net net1 = d.createNet("button1_IBUF"); + net1.connect(button1, "O"); + net1.connect(and2, "I1"); + + // Connect the LUT2 (AND2) to the LED IO + Net net2 = d.createNet("and2"); + net2.connect(and2, "O"); + net2.connect(led0, "I"); + + new Router(d).routeDesign(); + + VivadoToolsHelper.assertFullyRouted(d); + } + @ParameterizedTest @CsvSource({ "xcvu3p-ffvc1517-1-i,N28", From 20dc53efc3bc49e492e1efeae62cd2d45dfd4505 Mon Sep 17 00:00:00 2001 From: Andrew Butt Date: Wed, 29 Apr 2026 16:42:44 -0600 Subject: [PATCH 2/7] Add test case for fixes to getCorrespondingTile when called on certain SLL tiles (#1360) * [DesignTools] Fix NPE in fullyUnplaceCell() (#1358) Signed-off-by: Chris Lavin * Add test case for fixes to getCorrespondingTile when called on certain SLL tiles Signed-off-by: Andrew Butt * Address comments Signed-off-by: Andrew Butt --------- Signed-off-by: Chris Lavin Signed-off-by: Andrew Butt Co-authored-by: Chris Lavin --- .../rapidwright/design/DesignTools.java | 6 +++-- .../rapidwright/design/TestDesignTools.java | 22 ++++++++++++++++++- .../xilinx/rapidwright/design/TestModule.java | 20 ++++++++++++++--- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/com/xilinx/rapidwright/design/DesignTools.java b/src/com/xilinx/rapidwright/design/DesignTools.java index 45b50d6adb..5abb57906a 100644 --- a/src/com/xilinx/rapidwright/design/DesignTools.java +++ b/src/com/xilinx/rapidwright/design/DesignTools.java @@ -1454,8 +1454,10 @@ private static void fullyUnplaceCellHelper(Cell cell, Map> String lut6 = bel.getName().replace('5', '6'); if (siteInst.getCell(lut6) == null) { SitePinInst a6Spi = siteInst.getSitePinInst(lut6.substring(0,2)); - siteInst.unrouteIntraSiteNet(a6Spi.getBELPin(), siteInst.getBELPin(lut6, "A6")); - handlePinRemovals(a6Spi, deferRemovals); + if (a6Spi != null) { + siteInst.unrouteIntraSiteNet(a6Spi.getBELPin(), siteInst.getBELPin(lut6, "A6")); + handlePinRemovals(a6Spi, deferRemovals); + } } } diff --git a/test/src/com/xilinx/rapidwright/design/TestDesignTools.java b/test/src/com/xilinx/rapidwright/design/TestDesignTools.java index 0027a181d8..9f6588c61c 100644 --- a/test/src/com/xilinx/rapidwright/design/TestDesignTools.java +++ b/test/src/com/xilinx/rapidwright/design/TestDesignTools.java @@ -35,7 +35,6 @@ import java.util.Set; import java.util.stream.Collectors; -import com.xilinx.rapidwright.device.Node; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -47,6 +46,7 @@ import com.xilinx.rapidwright.device.BEL; import com.xilinx.rapidwright.device.BELPin; import com.xilinx.rapidwright.device.Device; +import com.xilinx.rapidwright.device.Node; import com.xilinx.rapidwright.device.PIP; import com.xilinx.rapidwright.device.Series; import com.xilinx.rapidwright.device.Site; @@ -1713,4 +1713,24 @@ public void testCreateCeClkOfRoutethruFFToVCC(String deviceName, String siteName Assertions.assertTrue(si.getNetFromSiteWire("FF_CLK_MOD_CLK_OUT").isGNDNet()); } } + + @Test + public void testFullyUnplaceCell() { + Design d = RapidWrightDCP.loadDCP("microblazeAndILA_3pblocks.dcp"); + + // Placed on a B5LUT where the B6LUT is unoccupied + Cell c = d.getCell("base_mb_i/mdm_1/U0/MDM_Core_I1/JTAG_CONTROL_I/Use_Serial_Unified_Completion.count[2]_i_1"); + + // Manufacturing a scenario where the A6 input pin is not there, seen in another design + SiteInst si = c.getSiteInst(); + SitePinInst spi = si.getSitePinInst("B6"); + Assertions.assertTrue(spi.getNet().isVCCNet()); + spi.getNet().removePin(spi, true); + si.removePin(spi); + + DesignTools.fullyUnplaceCell(c, null); + + Assertions.assertFalse(c.isPlaced()); + Assertions.assertNull(c.getBEL()); + } } diff --git a/test/src/com/xilinx/rapidwright/design/TestModule.java b/test/src/com/xilinx/rapidwright/design/TestModule.java index bf6c04e1e5..d13f8e5e0d 100644 --- a/test/src/com/xilinx/rapidwright/design/TestModule.java +++ b/test/src/com/xilinx/rapidwright/design/TestModule.java @@ -23,11 +23,11 @@ package com.xilinx.rapidwright.design; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - import com.xilinx.rapidwright.device.Device; +import com.xilinx.rapidwright.device.Tile; import com.xilinx.rapidwright.examples.AddSubGenerator; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; public class TestModule { @@ -56,4 +56,18 @@ public void testModuleSLRRelocate() { Assertions.assertTrue(slrAdder.isValidPlacement(slrAdder.getAnchor(), top)); Assertions.assertFalse(slrAdder.isValidPlacement(noSLRAdder.getAnchor(), top)); } + + @Test + public void testGetCorrespondingTile() { + Device v80 = Device.getDevice("xcv80"); + + // Testing for SLL to SLL_1 special case because they have the same tile type but overlapping X,Y grids + Tile templateTile = v80.getTile("SLL_X23Y886"); + Tile originalAnchor = v80.getTile("CLE_W_CORE_X22Y886"); + Tile newAnchorTile = v80.getTile("CLE_W_CORE_X23Y900"); + Tile newTile = Module.getCorrespondingTile(templateTile, newAnchorTile, originalAnchor); + + Assertions.assertNotNull(newTile); + Assertions.assertEquals("SLL_1_X23Y900", newTile.getName()); + } } From a30a1ad842beca0a47a2da52f49a13b7f3587907 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Wed, 3 Jun 2026 13:29:14 -0600 Subject: [PATCH 3/7] Array Builder Tool - Replicate Optimized Kernels in an Array (#1163) * Merge master Signed-off-by: Chris Lavin * WIP - ArrayBuilder Signed-off-by: Chris Lavin * Add missing header, import Signed-off-by: Chris Lavin * Adds a skip implementation option Signed-off-by: Chris Lavin * Avoids NPEs Signed-off-by: Chris Lavin * Options for array construction Signed-off-by: Chris Lavin * Adding ArrayBuilder to main entrypoint Signed-off-by: Chris Lavin * Copyright Signed-off-by: Eddie Hung * Add option to provide a top-level design Signed-off-by: Chris Lavin * Work-around for blackbox handling Signed-off-by: Chris Lavin * Update library when renaming cells; add API to remove bb prop Signed-off-by: Chris Lavin * Inline Flop Add/Removal Tool Signed-off-by: Chris Lavin * rc1 Signed-off-by: Chris Lavin * Adding a test Signed-off-by: Chris Lavin * Merge encrypted cells from modules Signed-off-by: Chris Lavin * 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 * Fix npe when no top dcp provided (#1301) Signed-off-by: Andrew Butt * 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 * [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 * 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 * Remove unnecessary methods, refactor name. Signed-off-by: Chris Lavin * Refactor Signed-off-by: Chris Lavin * 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 * [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 * [NetlistBrowser] Cell Instance Schematic Viewer (#1322) * Initial Schematic viewer with several issues. Signed-off-by: Chris Lavin * Fix minor issues Signed-off-by: Chris Lavin * Fixes minor rendering issues Signed-off-by: Chris Lavin * Fixes around labeling and refactoring Signed-off-by: Chris Lavin * Fix top port labels Signed-off-by: Chris Lavin * Fix hier-cell pin labels Signed-off-by: Chris Lavin * Fix hier button, still needs work for internal routes Signed-off-by: Chris Lavin * Fixes nets inside expanded cells Signed-off-by: Chris Lavin * Move button to top left corner of expanded cells Signed-off-by: Chris Lavin * Fix padding/spacing around labels and buttons Signed-off-by: Chris Lavin * Fixing multiple hier view, switch from EDIFCell->EDIFHierCellInst Signed-off-by: Chris Lavin * Basic selection using Strings, still WIP Signed-off-by: Chris Lavin * Fixes various bugs Signed-off-by: Chris Lavin * Refactor to use all EDIFHier classes Signed-off-by: Chris Lavin * Fix disconnected ports Signed-off-by: Chris Lavin * Fix NPE Signed-off-by: Chris Lavin * Fix multi-level hierarchy issue. Signed-off-by: Chris Lavin * Fixing Z layers Signed-off-by: Chris Lavin * Update for ELK and Guava license inclusion. Signed-off-by: Chris Lavin * Cleanup Signed-off-by: Chris Lavin * Temporarily remove cache Signed-off-by: Chris Lavin * Restore cache entry Signed-off-by: Chris Lavin * Update Jar test Signed-off-by: Chris Lavin * List jars Signed-off-by: Chris Lavin * Rename cache Signed-off-by: Chris Lavin * Try new gradle home dir Signed-off-by: Chris Lavin * More debug Signed-off-by: Chris Lavin * Remove env Signed-off-by: Chris Lavin * Fix NPE on inner port Signed-off-by: Chris Lavin * Enable top selection Signed-off-by: Chris Lavin * Add zoom to fit when selecting a new cell. Signed-off-by: Chris Lavin * Workaround to support JDK8 execution with ELK Signed-off-by: Chris Lavin * Fix selection of unexpanded tree items Signed-off-by: Chris Lavin * Simplify Signed-off-by: Chris Lavin * Attempt to allow selection from Python prompt Signed-off-by: Chris Lavin * Select from main thread Signed-off-by: Chris Lavin * get startup sync correct Signed-off-by: Chris Lavin * Fixing more issues, still WIP Signed-off-by: Chris Lavin * Fix missing top-level ports in the tree browser. Signed-off-by: Chris Lavin * Fix various NPEs, top port issues Signed-off-by: Chris Lavin * Cleanup after window closes Signed-off-by: Chris Lavin * Adds right-click copy menu on tree items Signed-off-by: Chris Lavin --------- Signed-off-by: Chris Lavin * 2025.2.0 devices (#1325) * Updates for 2025.2.0 devices Signed-off-by: Chris Lavin * Update 2025.2.0 devices Signed-off-by: Chris Lavin * rc4 Signed-off-by: Chris Lavin * rc5 Signed-off-by: Chris Lavin --------- Signed-off-by: Chris Lavin * Adds external library for netlists to compensate for black boxed cells (#1294) * Adds external library for netlists to compensate for black boxed cells Signed-off-by: Chris Lavin * Removing overconstrained check Signed-off-by: Chris Lavin * Add additional validity check in new EDIFPortInst() Signed-off-by: Chris Lavin * Fix failing test case after EDIFPortInst check Signed-off-by: Chris Lavin * Adds support for negative indexed busses on ports Signed-off-by: Chris Lavin * Adds example test of how to run Signed-off-by: Chris Lavin * Skip multiply_ip.dcp Signed-off-by: Chris Lavin * Test fix Signed-off-by: Chris Lavin * Updating RapidWrightDCP Signed-off-by: Chris Lavin --------- Signed-off-by: Chris Lavin * [VersalClockRouting] Mark sink pins as routed once LCBs are connected (#1326) Signed-off-by: Chris Lavin * rc6 Signed-off-by: Chris Lavin * [DesignTools] Parallelize preprocessing methods (#1324) * Parallelize router initialization Signed-off-by: Andrew Butt * Start improving parallelism Signed-off-by: Andrew Butt * Parallelize createMissingSitePinInsts Signed-off-by: Andrew Butt * Clean up parallelization Signed-off-by: Andrew Butt * Switch to using futures to prevent accidentally hiding exceptions thrown in threads Signed-off-by: Andrew Butt * Fix NPE Signed-off-by: Andrew Butt * Fix concurrent modification issues Signed-off-by: Andrew Butt * Fix another concurrent modification issue Signed-off-by: Andrew Butt * Reduce synchronization and add caching on cells and pins Signed-off-by: Andrew Butt * Address comments Signed-off-by: Andrew Butt * Add back sync Signed-off-by: Andrew Butt * Trying to fix failing test case Signed-off-by: Andrew Butt * Fix accidentally skipping some nets Signed-off-by: Andrew Butt * Cleanup Signed-off-by: Andrew Butt * Add synchronization for rare case Signed-off-by: Andrew Butt * Remove fall-through if statements Signed-off-by: Andrew Butt * Fix additional early returns Signed-off-by: Andrew Butt * Use join instead of joinFirst Signed-off-by: Andrew Butt * remove cell cache Signed-off-by: Andrew Butt * Remove re-lookup of cell Signed-off-by: Andrew Butt * Switch to integer map Signed-off-by: Andrew Butt * Fix bug from switching to integer site wires map Signed-off-by: Andrew Butt * Cleanup Signed-off-by: Andrew Butt * Add back site wires copy Signed-off-by: Andrew Butt * Add comment on synchronization Signed-off-by: Andrew Butt * [DesignTools] Simplify Simplify createMissingSitePinInsts() Change getAllRoutedSitePinsFromPhysicalPin() Remove getSiteInstToNetSiteWiresMap() Signed-off-by: Eddie Hung * Cleanup Signed-off-by: Eddie Hung * Comments Signed-off-by: Eddie Hung * createMissingSitePinInsts() to depend on makePhysNetNameConsistent() Thereby not needing to do parent net checks Signed-off-by: Eddie Hung * Cleanup Signed-off-by: Eddie Hung * Fix tests Signed-off-by: Eddie Hung * Undo accidental Signed-off-by: Eddie Hung * [MergeDesigns] Clear logical net from physical net Signed-off-by: Eddie Hung * Double check before renaming Signed-off-by: Eddie Hung * Tidy Signed-off-by: Eddie Hung * getSitePinInst() to be synchronized Signed-off-by: Eddie Hung * CounterGenerator to flatten design Signed-off-by: Eddie Hung * Design.get{Gnd,Vcc}Net() outside of multi-threaded section Signed-off-by: Eddie Hung * Restore comment Signed-off-by: Eddie Hung * No less than 100 objects per job Signed-off-by: Eddie Hung * Fix TestNet Signed-off-by: Eddie Hung * [TestSiteInst] Add testAddPinDuplicate() Signed-off-by: Eddie Hung * Refactor Signed-off-by: Eddie Hung * Restore submodule Signed-off-by: Eddie Hung * Address review comments Signed-off-by: Eddie Hung --------- Signed-off-by: Andrew Butt Signed-off-by: Eddie Hung Signed-off-by: Chris Lavin Co-authored-by: Andrew Butt Co-authored-by: Chris Lavin * [VersalClockRouting] VDISTR tree storage map for Versal (#1311) * [VersalClockRouting] VDISTR tree storage map for Versal Signed-off-by: Chris Lavin * Preliminary v80 support Signed-off-by: Chris Lavin * Refactoring to use preferred clk root; adds VersalClockTree object Signed-off-by: Chris Lavin * Update storage format and file for vdistr paths on Versal Signed-off-by: Chris Lavin * Increasing the search space for clock root column candidates Signed-off-by: Chris Lavin * Apply suggestions from code review Signed-off-by: Chris Lavin Signed-off-by: Chris Lavin * Add an assert Signed-off-by: Chris Lavin * Adds all Versal devices (except vp1902) Signed-off-by: Chris Lavin * Adding test to check that Versal distribution template in use Signed-off-by: Chris Lavin * New data file fixes missing entries Signed-off-by: Chris Lavin --------- Signed-off-by: Chris Lavin * 2025.2.0 jar Signed-off-by: Chris Lavin * [RWRoute] Initial Versal SLR crossing support (#1302) * [TestRWRoute] Initial testSLRCrossingNonTimingDriven for Versal Signed-off-by: Eddie Hung * [RWRoute] Initial Versal SLR crossing support Signed-off-by: Eddie Hung * More tests Signed-off-by: Eddie Hung * [RouteNodeGraph] Dynamic SLL length Signed-off-by: Eddie Hung * Account for overshooting in Y due to SLLs Signed-off-by: Eddie Hung * Tidy Signed-off-by: Eddie Hung * One more test Signed-off-by: Eddie Hung * Tidy Signed-off-by: Eddie Hung * Track NODE_SLL_DATA usage Signed-off-by: Eddie Hung * Fix end tile computation for SLL tiles Signed-off-by: Eddie Hung * Refactor routethru check Signed-off-by: Eddie Hung * Comments Signed-off-by: Eddie Hung * Only check NODE_CLE_BNODE for reaching SLRs Signed-off-by: Eddie Hung * Make INODEs for inter-SLR connections accessible Allowing INODE -> BOUNCE -> BNODE -> SLL_INPUT Signed-off-by: Eddie Hung * RWRoute Versal SLR Crossing Test (#1327) * Bump RapidWrightDCP Signed-off-by: Andrew Butt * Add Versal SLR crossing rwroute test Signed-off-by: Andrew Butt --------- Signed-off-by: Andrew Butt * Do not unroute since RWRoute unroutes anyway Signed-off-by: Eddie Hung * Incorporate fixed DCP Signed-off-by: Chris Lavin * Fix bad GUI constructor Signed-off-by: Chris Lavin * Fix PBlockRange XDC parser to handle multiple ranges in same cmd Signed-off-by: Chris Lavin * Updating submodule RapidWrightDCP Signed-off-by: Chris Lavin * Tidy up Signed-off-by: Eddie Hung * Redundant Signed-off-by: Eddie Hung * Check grandparent instead of regex Signed-off-by: Eddie Hung * Update comment Signed-off-by: Eddie Hung * Test that no routethrus are used when terminating at LAG pin for FF Signed-off-by: Eddie Hung * Apply suggestions from code review Signed-off-by: eddieh-xlnx --------- Signed-off-by: Eddie Hung Signed-off-by: Andrew Butt Signed-off-by: Chris Lavin Signed-off-by: eddieh-xlnx Co-authored-by: Andrew Butt Co-authored-by: Chris Lavin * Add ability to constrain flop harness to specific side of PBlock (#1318) * Add ability to constraint InlineFlops to a specific side of the PBlock to improve routability. Also unroute top-level I/O nets that exit the pblock. Signed-off-by: Andrew Butt * Use regex for port side map Signed-off-by: Andrew Butt * Address comments Signed-off-by: Andrew Butt * Update to new getBitBlastedIndices Signed-off-by: Andrew Butt * Remove unnecessary function calls Signed-off-by: Andrew Butt * Add test case of unrouteTopLevelNetsThatLeavePBlock Signed-off-by: Andrew Butt * Remove extra imports Signed-off-by: Andrew Butt * Add initial removeInlineFlops test Signed-off-by: Andrew Butt * Test remove inline flops and remove unnecessary code Signed-off-by: Andrew Butt * Add back pin removal because of the specific case where a flop is in the same slice as a ground source Signed-off-by: Andrew Butt * Address comments and add new test cases Signed-off-by: Andrew Butt * Cleanup Signed-off-by: Andrew Butt * Update based on comment Signed-off-by: Andrew Butt --------- Signed-off-by: Andrew Butt * Automate placement in ArrayBuilder (#1319) * Add ability to constraint InlineFlops to a specific side of the PBlock to improve routability. Also unroute top-level I/O nets that exit the pblock. Signed-off-by: Andrew Butt * Automate placement in ArrayBuilder Signed-off-by: Andrew Butt * Use regex for port side map Signed-off-by: Andrew Butt * Address comments Signed-off-by: Andrew Butt * Update to new getBitBlastedIndices Signed-off-by: Andrew Butt * Remove unnecessary function calls Signed-off-by: Andrew Butt * Add test case of unrouteTopLevelNetsThatLeavePBlock Signed-off-by: Andrew Butt * Remove extra imports Signed-off-by: Andrew Butt * Add initial removeInlineFlops test Signed-off-by: Andrew Butt * Test remove inline flops and remove unnecessary code Signed-off-by: Andrew Butt * Add back pin removal because of the specific case where a flop is in the same slice as a ground source Signed-off-by: Andrew Butt * Add ability to route array builder design Signed-off-by: Andrew Butt * Remove hardcoded placement constraints Signed-off-by: Andrew Butt * Handle null case Signed-off-by: Andrew Butt * Address comments and add new test cases Signed-off-by: Andrew Butt * Cleanup Signed-off-by: Andrew Butt * Address comments Signed-off-by: Andrew Butt --------- Signed-off-by: Andrew Butt * Array builder refactor (#1332) * Add ability to constraint InlineFlops to a specific side of the PBlock to improve routability. Also unroute top-level I/O nets that exit the pblock. Signed-off-by: Andrew Butt * Automate placement in ArrayBuilder Signed-off-by: Andrew Butt * Use regex for port side map Signed-off-by: Andrew Butt * Address comments Signed-off-by: Andrew Butt * Update to new getBitBlastedIndices Signed-off-by: Andrew Butt * Remove unnecessary function calls Signed-off-by: Andrew Butt * Add test case of unrouteTopLevelNetsThatLeavePBlock Signed-off-by: Andrew Butt * Remove extra imports Signed-off-by: Andrew Butt * Add initial removeInlineFlops test Signed-off-by: Andrew Butt * Test remove inline flops and remove unnecessary code Signed-off-by: Andrew Butt * Add back pin removal because of the specific case where a flop is in the same slice as a ground source Signed-off-by: Andrew Butt * Add ability to route array builder design Signed-off-by: Andrew Butt * Remove hardcoded placement constraints Signed-off-by: Andrew Butt * Handle null case Signed-off-by: Andrew Butt * Address comments and add new test cases Signed-off-by: Andrew Butt * Cleanup Signed-off-by: Andrew Butt * Start refactoring array builder Signed-off-by: Andrew Butt * Refactor mostly complete Signed-off-by: Andrew Butt * Time flop harness creation Signed-off-by: Andrew Butt * More ArrayBuilder refactoring Signed-off-by: Andrew Butt * Move main Signed-off-by: Andrew Butt * Fix array builder failure Signed-off-by: Andrew Butt * Marge origin/array_builder_placement Signed-off-by: Andrew Butt * Add javadoc Signed-off-by: Andrew Butt --------- Signed-off-by: Andrew Butt * [ArrayBuilder] Avoid failure when reading from placement file (#1354) Signed-off-by: Chris Lavin * Correctly tie BUFGCE CE pin to VCC for Versal (#1368) Signed-off-by: Chris Lavin --------- Signed-off-by: Chris Lavin Signed-off-by: Eddie Hung Signed-off-by: Andrew Butt Signed-off-by: Andrew Butt Signed-off-by: Jakob Wenzel Signed-off-by: coherent17 Signed-off-by: eddieh-xlnx Co-authored-by: Eddie Hung Co-authored-by: Andrew Butt Co-authored-by: Andrew Butt Co-authored-by: Jakob Wenzel Co-authored-by: Coherent17 --- .../xilinx/rapidwright/MainEntrypoint.java | 2 + .../xilinx/rapidwright/design/ClockTools.java | 102 ++ .../rapidwright/design/DesignTools.java | 58 ++ .../xilinx/rapidwright/design/NetTools.java | 87 ++ .../design/blocks/PBlockGenerator.java | 82 +- .../rapidwright/design/blocks/PBlockSide.java | 31 + .../design/blocks/UtilizationType.java | 142 ++- .../design/tools/ArrayBuilder.java | 923 ++++++++++++++++++ .../design/tools/ArrayBuilderConfig.java | 480 +++++++++ .../design/tools/ArrayNetlistGraph.java | 439 +++++++++ .../design/tools/InlineFlopTools.java | 421 +++++++- .../xdc/UnsupportedConstraintElement.java | 9 - src/com/xilinx/rapidwright/device/Series.java | 2 +- .../rapidwright/eco/ECOPlacementHelper.java | 4 +- src/com/xilinx/rapidwright/edif/EDIFCell.java | 30 +- .../xilinx/rapidwright/edif/EDIFTools.java | 25 +- .../rapidwright/util/PerformanceExplorer.java | 268 ++++- src/com/xilinx/rapidwright/util/Utils.java | 3 +- .../xilinx/rapidwright/util/VivadoTools.java | 52 +- .../rapidwright/design/TestNetTools.java | 82 +- .../design/blocks/TestUtilizationType.java | 53 + .../design/tools/TestInlineFlopTools.java | 214 ++++ .../xilinx/rapidwright/eco/TestECOTools.java | 1 - 23 files changed, 3346 insertions(+), 164 deletions(-) create mode 100644 src/com/xilinx/rapidwright/design/ClockTools.java create mode 100644 src/com/xilinx/rapidwright/design/blocks/PBlockSide.java create mode 100644 src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java create mode 100644 src/com/xilinx/rapidwright/design/tools/ArrayBuilderConfig.java create mode 100644 src/com/xilinx/rapidwright/design/tools/ArrayNetlistGraph.java create mode 100644 test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java create mode 100644 test/src/com/xilinx/rapidwright/design/tools/TestInlineFlopTools.java diff --git a/src/com/xilinx/rapidwright/MainEntrypoint.java b/src/com/xilinx/rapidwright/MainEntrypoint.java index e8f963ecc8..87a8afd2e8 100644 --- a/src/com/xilinx/rapidwright/MainEntrypoint.java +++ b/src/com/xilinx/rapidwright/MainEntrypoint.java @@ -38,6 +38,7 @@ import com.xilinx.rapidwright.design.blocks.PBlockGenerator; import com.xilinx.rapidwright.design.compare.DesignComparator; import com.xilinx.rapidwright.design.merge.MergeDesigns; +import com.xilinx.rapidwright.design.tools.ArrayBuilder; import com.xilinx.rapidwright.design.tools.CopyImplementation; import com.xilinx.rapidwright.design.tools.DesignObfuscator; import com.xilinx.rapidwright.design.tools.LUTTools; @@ -136,6 +137,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); diff --git a/src/com/xilinx/rapidwright/design/ClockTools.java b/src/com/xilinx/rapidwright/design/ClockTools.java new file mode 100644 index 0000000000..25063c0e9c --- /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/DesignTools.java b/src/com/xilinx/rapidwright/design/DesignTools.java index 5abb57906a..59dd09251b 100644 --- a/src/com/xilinx/rapidwright/design/DesignTools.java +++ b/src/com/xilinx/rapidwright/design/DesignTools.java @@ -3451,6 +3451,64 @@ public static void makePhysNetNamesConsistent(Design design) { ParallelismTools.join(futures); } + /** + * Make a single physical Net's name consistent with its logical (EDIF) netlist. + * @param design Design containing the net + * @param net The physical Net to make consistent + * @return The parent physical Net or null if already consistent + */ + public static Net makePhysNetNameConsistent(Design design, Net net) { + if (net.isStaticNet()) { + Net staticNet = design.getStaticNet(net.getType()); + if (staticNet != net) { + design.movePinsToNewNetDeleteOldNet(net, staticNet, true); + return staticNet; + } + return null; + } + + EDIFHierNet hierNet = net.getLogicalHierNet(); + if (hierNet == null) { + return null; // Likely an encrypted cell + } + + Map netParentMap = design.getNetlist().getParentNetMap(); + EDIFHierNet parentHierNet = netParentMap.get(hierNet); + if (parentHierNet == null) { + return null; + } + + // Check for static net aliases + EDIFNet srcNetAlias = parentHierNet.getNet(); + if (srcNetAlias.isGND()) { + Net gndNet = design.getGndNet(); + design.movePinsToNewNetDeleteOldNet(net, gndNet, true); + return gndNet; + } else if (srcNetAlias.isVCC()) { + Net vccNet = design.getVccNet(); + design.movePinsToNewNetDeleteOldNet(net, vccNet, true); + return vccNet; + } + + if (!hierNet.equals(parentHierNet)) { + String parentNetName = parentHierNet.getHierarchicalNetName(); + Net parentPhysNet = design.getNet(parentNetName); + + if (parentPhysNet == null) { + // No existing parent net - just rename this one + if (!net.rename(parentNetName)) { + System.out.println("WARNING: Failed to change physical net name " + net.getName()); + } + } else { + // Parent net exists - merge into it + design.movePinsToNewNetDeleteOldNet(net, parentPhysNet, true); + return parentPhysNet; + } + } + + return null; + } + public static void createPossiblePinsToStaticNets(Design design) { createA1A6ToStaticNets(design); createCeClkOfRoutethruFFToVCC(design); diff --git a/src/com/xilinx/rapidwright/design/NetTools.java b/src/com/xilinx/rapidwright/design/NetTools.java index c25e1ee00f..acda5cbf72 100644 --- a/src/com/xilinx/rapidwright/design/NetTools.java +++ b/src/com/xilinx/rapidwright/design/NetTools.java @@ -33,10 +33,14 @@ import java.util.Set; import java.util.function.Function; +import com.xilinx.rapidwright.design.blocks.PBlock; import com.xilinx.rapidwright.device.IntentCode; import com.xilinx.rapidwright.device.Node; import com.xilinx.rapidwright.device.PIP; import com.xilinx.rapidwright.device.SiteTypeEnum; +import com.xilinx.rapidwright.edif.EDIFHierNet; +import com.xilinx.rapidwright.edif.EDIFPort; +import com.xilinx.rapidwright.edif.EDIFPortInst; public class NetTools { public static final String CONTINUE_ELBOW = "\u251c\u2500 "; @@ -262,6 +266,52 @@ 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 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()) { + 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); + } + } + overlappingNets.add(existing); + } + } + } + } + return overlappingNets; + } + /** * Returns a string representation of the net's routing tree using tree * characters (├─, └─). @@ -471,4 +521,41 @@ public static Node findClockRootVRoute(Net net) { return deepestVRoute; } + + /** + * Unroute top-level port nets that have PIPs outside the specified PBlock. Vivado can potentially create designs + * where the top-level port nets leave the PBlock if the InlineFlopTools flop harness was used and the top-level + * port net also has a sink inside the PBlock. Physical net names must be consistent and macro unisims must be + * expanded before calling this method. + * + * @param d The design to unroute nets from. + * @param pBlock The bounding box for the placed and routed design. + */ + public static List unrouteTopLevelNetsThatLeavePBlock(Design d, PBlock pBlock) { + List unroutedNets = new ArrayList<>(); + for (EDIFPort p : d.getNetlist().getTopCell().getPorts()) { + for (int i : p.getBitBlastedIndices()) { + EDIFPortInst portInst = p.getInternalPortInstFromIndex(i); + if (portInst == null) { + continue; + } + EDIFHierNet hierNet = new EDIFHierNet(d.getNetlist().getTopHierCellInst(), portInst.getNet()); + EDIFHierNet parentNet = d.getNetlist().getParentNet(hierNet); + Net net = d.getNet(parentNet.getHierarchicalNetName()); + + boolean leavesPBlock = false; + for (PIP pip : net.getPIPs()) { + if (!pBlock.containsTile(pip.getTile())) { + leavesPBlock = true; + break; + } + } + if (leavesPBlock) { + net.unroute(); + unroutedNets.add(net); + } + } + } + return unroutedNets; + } } diff --git a/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java b/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java index 4b35c2088c..8899996be7 100644 --- a/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java +++ b/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java @@ -68,8 +68,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 +106,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 +152,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 +176,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 +769,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 +942,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 +1118,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 +1162,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/PBlockSide.java b/src/com/xilinx/rapidwright/design/blocks/PBlockSide.java new file mode 100644 index 0000000000..fccd60daa3 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/blocks/PBlockSide.java @@ -0,0 +1,31 @@ +/* + * + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Andrew Butt, 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; + +public enum PBlockSide { + LEFT, + RIGHT, + TOP, + BOTTOM +} diff --git a/src/com/xilinx/rapidwright/design/blocks/UtilizationType.java b/src/com/xilinx/rapidwright/design/blocks/UtilizationType.java index cc8bd9d5fb..9f59931b44 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,17 @@ 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"), + BRAMS("BRAMS"), LOOKAHEAD8("LOOKAHEAD8"); private String name; @@ -64,4 +72,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 0000000000..3bf7a6136a --- /dev/null +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java @@ -0,0 +1,923 @@ +/* + * + * 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.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.Objects; +import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.Cell; +import com.xilinx.rapidwright.design.ClockTools; +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.DesignTools; +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.NetType; +import com.xilinx.rapidwright.design.RelocatableTileRectangle; +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.design.blocks.PBlockSide; +import com.xilinx.rapidwright.device.BEL; +import com.xilinx.rapidwright.device.ClockRegion; +import com.xilinx.rapidwright.device.Device; +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.EDIFHierCellInst; +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.EDIFTools; +import com.xilinx.rapidwright.edif.EDIFValueType; +import com.xilinx.rapidwright.rwroute.PartialCUFR; +import com.xilinx.rapidwright.rwroute.PartialRouter; +import com.xilinx.rapidwright.tests.CodePerfTracker; +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 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 Design kernelDesign; + + private Design topDesign; + + private Design array; + + private String kernelClockName; + + private String topClockName; + + private List pblocks; + + private CodePerfTracker t; + + private ArrayNetlistGraph condensedGraph; + + private Map newPlacementMap; + + private List modules; + + private List modInstNames; + + private final ArrayBuilderConfig config; + + public ArrayBuilder(ArrayBuilderConfig config) { + newPlacementMap = new HashMap<>(); + modInstNames = new ArrayList<>(); + modules = new ArrayList<>(); + this.config = config; + } + + public ArrayBuilder(ArrayBuilderConfig config, CodePerfTracker t) { + this(config); + this.t = t; + } + + 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 getKernelDesign().getDevice(); + } + + public Design getKernelDesign() { + return kernelDesign; + } + + public Design getTopDesign() { + return topDesign; + } + + public Design getArray() { + return array; + } + + public String getKernelClockName() { + return kernelClockName; + } + + public String getTopClockName() { + return topClockName; + } + + public void setPBlocks(List pblocks) { + this.pblocks = pblocks; + } + + public List getPBlocks() { + return pblocks == null ? Collections.emptyList() : pblocks; + } + + public ArrayNetlistGraph getCondensedGraph() { + return condensedGraph; + } + + public void setCondensedGraph(ArrayNetlistGraph condensedGraph) { + this.condensedGraph = condensedGraph; + } + + private Map getNewPlacementMap() { + return newPlacementMap; + } + + private void initializeArrayBuilder() { + assert (config.getKernelDesign() != null); + + kernelDesign = config.getKernelDesign(); + topDesign = config.getTopDesign(); + + if (config.getPBlockStrings() != null) { + List pblocks = new ArrayList(); + for (String str : config.getPBlockStrings()) { + pblocks.add(new PBlock(getDevice(), str)); + } + setPBlocks(pblocks); + } else if (!config.isSkipImpl()) { + PBlockGenerator pb = new PBlockGenerator(); + Path utilReport; + Path shapesReport; + if (config.getUtilReport() != null && config.getShapesReport() != null) { + utilReport = Paths.get(config.getUtilReport()); + shapesReport = Paths.get(config.getShapesReport()); + } else { + utilReport = Paths.get("utilization.report"); + shapesReport = Paths.get("shapes.report"); + Path kernelDesign = Paths.get("kernelDesign.dcp"); + config.getKernelDesign().writeCheckpoint(kernelDesign); + VivadoTools.getUtilizationAndShapesReport(kernelDesign, 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 (config.getTopDesign() != null) { + EDIFTools.removeVivadoBusPreventionAnnotations(config.getTopDesign().getNetlist()); + } + + if (config.getKernelClockName() != null) { + kernelClockName = config.getKernelClockName(); + } else { + kernelClockName = ClockTools.getClockFromDesign(config.getKernelDesign()).toString(); + } + + if (config.getTopClockName() != null) { + topClockName = config.getTopClockName(); + } else if (config.getTopDesign() != null) { + topClockName = ClockTools.getClockFromDesign(config.getTopDesign()).toString(); + } + } + + 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 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 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) { + if (fileName == null) { + throw new RuntimeException("Trying to read placement file without providing a file path"); + } + 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 List> getValidPlacementGrid(Module module) { + List> placementGrid = new ArrayList<>(); + // Sort by descending Y coordinate, then ascending X coordinate + List sortedValidPlacements = module.getAllValidPlacements().stream().sorted((s1, s2) -> { + if (s1.getInstanceY() == s2.getInstanceY()) { + return s1.getInstanceX() - s2.getInstanceX(); + } + return s2.getInstanceY() - s1.getInstanceY(); + }).collect(Collectors.toList()); + int currentYCoordinate = sortedValidPlacements.get(0).getInstanceY(); + int i = 0; + placementGrid.add(new ArrayList<>()); + for (Site anchor : sortedValidPlacements) { + if (anchor.getInstanceY() < currentYCoordinate) { + i++; + placementGrid.add(new ArrayList<>()); + } + placementGrid.get(i).add(anchor); + currentYCoordinate = anchor.getInstanceY(); + } + return placementGrid; + } + + private List implementKernel(Path workDir) { + t.stop().start("Implement Kernel"); + FileTools.makeDirs(workDir.toString()); + System.out.println("[INFO] Created work directory: " + workDir.toString()); + + // Initialize PerformanceExplorer + PerformanceExplorer pe = new PerformanceExplorer(getKernelDesign(), workDir.toString(), + kernelClockName, config.getClockPeriod()); + + // Set PBlocks + Map pblocks = new HashMap<>(); + for (PBlock pb : getPBlocks()) { + pblocks.put(pb, null); + } + pe.setPBlocks(pblocks); + pe.setAddEDIFAndMetadata(true); + pe.setReusePreviousResults(config.isReuseResults()); + pe.explorePerformance(); + + List modules = new ArrayList<>(); + 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, config.shouldUnrouteStaticNets()); + 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()); + } + return modules; + } + + private List, String>> calculateIdealArrayPlacement() { + t.stop().start("Calculate ideal array placement"); + // Find instances in existing design + modInstNames = getMatchingModuleInstanceNames(modules.get(0), array); + if (modInstNames.isEmpty()) { + throw new RuntimeException("Failed to find module instances in top design that match kernel interface"); + } + config.setInstCountLimit(modInstNames.size()); + Map sideMap = null; + if (config.getSideMapFile() != null) { + sideMap = InlineFlopTools.parseSideMap(getKernelDesign().getNetlist(), config.getSideMapFile()); + } + setCondensedGraph(new ArrayNetlistGraph(array, modInstNames, sideMap)); + Map, String> idealPlacement = + getCondensedGraph().getGreedyPlacementGrid(); + return idealPlacement.entrySet().stream() + .map((e) -> new Pair<>(e.getKey(), e.getValue())) + .sorted((p1, p2) -> { + Pair pa = p1.getFirst(); + Pair pb = p2.getFirst(); + if (!Objects.equals(pa.getSecond(), pb.getSecond())) { + return pa.getSecond().compareTo(pb.getSecond()); + } + + return pa.getFirst().compareTo(pb.getFirst()); + }) + .collect(Collectors.toList()); + } + + private List, String>> prepareArrayForPlacement() { + Path workDir = Paths.get(config.getWorkDir()); + if (!config.isSkipImpl()) { + modules = implementKernel(workDir); + } else /* skipImpl==true */ { + // Just use the design we loaded and replicate it + t.stop().start("Calculate Valid Placements"); + removeBUFGs(config.getKernelDesign()); + if (config.shouldUnrouteStaticNets()) { + Net gndNet = config.getKernelDesign().getNet(Net.GND_NET); + if (gndNet != null) { + gndNet.unroute(); + List staticSourcePins = new ArrayList<>(); + Set staticSourceSites = new HashSet<>(); + for (SitePinInst pin : gndNet.getPins()) { + if (pin.isOutPin() && pin.getSiteInst().getName().startsWith(SiteInst.STATIC_SOURCE)) { + staticSourcePins.add(pin); + staticSourceSites.add(pin.getSiteInst()); + } + } + for (SitePinInst pin : staticSourcePins) { + gndNet.removePin(pin); + pin.getSiteInst().removePin(pin); + } + for (SiteInst siteInst : staticSourceSites) { + siteInst.setDesign(null); + siteInst.unPlace(); + } + } + Net vccNet = getKernelDesign().getNet(Net.VCC_NET); + if (vccNet != null) { + vccNet.unroute(); + } + } + Module m = new Module(getKernelDesign(), config.shouldUnrouteStaticNets()); + m.getNet(getKernelClockName()).unroute(); + + if (config.getInputPlacementFileName() == null) { + m.calculateAllValidPlacements(getDevice()); + } + if (!getPBlocks().isEmpty()) { + m.setPBlock(getPBlocks().get(0)); + } + modules.add(m); + } + + // List containing pairs of (x,y) coordinates with the moduleInst name placed at that ideal (x,y) coordinate + List, String>> idealPlacementList = null; + if (getTopDesign() == null) { + array = new Design("array", getKernelDesign().getPartName()); + } else { + array = getTopDesign(); + if (config.getInputPlacementFileName() == null) { + idealPlacementList = calculateIdealArrayPlacement(); + } else { + // Placement from file, also still need to find matching module instances + modInstNames = getMatchingModuleInstanceNames(modules.get(0), array); + if (modInstNames.isEmpty()) { + throw new RuntimeException("Failed to find module instances in top design that match kernel interface"); + } + config.setInstCountLimit(modInstNames.size()); + } + } + return idealPlacementList; + } + + private void placeInstancesWithManualPlacementFile() { + Map placementMap = readPlacementFromFile(config.getInputPlacementFileName()); + System.out.println("Placing from specified file"); + int placed = 0; + 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()); + } + } + + private void placeModuleInstancesAutomatically(List, String>> idealPlacementList) { + int placed = 0; + ModuleInst curr = null; + int i = 0; + + // TODO: Figure out how to handle placement for multiple modules + Module module = modules.get(0); + RelocatableTileRectangle boundingBox = module.getBoundingBox(); + List boundingBoxes = new ArrayList<>(); + List> validPlacementGrid = getValidPlacementGrid(module); + int gridX = 0; + int gridY = 5; + int lastYCoordinate = 0; + boolean searchDown = true; + while (placed < config.getInstCountLimit()) { + if (curr == null) { + String instName = modInstNames == null ? ("inst_" + i) : idealPlacementList.get(i).getSecond(); + int yCoordinate = idealPlacementList.get(i).getFirst().getSecond(); + if (yCoordinate > lastYCoordinate) { + gridX = 0; + searchDown = true; + } + lastYCoordinate = yCoordinate; + curr = array.createModuleInst(instName, module); + i++; + } + if (gridY >= validPlacementGrid.size()) { + throw new RuntimeException("Optimal placement is too tall for device"); + } + if (gridX >= validPlacementGrid.get(gridY).size()) { + throw new RuntimeException("Optimal placement is too wide for device"); + } + Site anchor = validPlacementGrid.get(gridY).get(gridX); + RelocatableTileRectangle newBoundingBox = + boundingBox.getCorresponding(anchor.getTile(), module.getAnchor().getTile()); + boolean noOverlap = boundingBoxes.stream().noneMatch((b) -> b.overlaps(newBoundingBox)); + if (config.isExactPlacement() || (noOverlap && !boundingBoxStraddlesClockRegion(newBoundingBox))) { + if (curr.place(anchor, true, false)) { + if (config.isExactPlacement() && (straddlesClockRegion(curr) + || !NetTools.getNetsWithOverlappingNodes(array).isEmpty()) + ) { + curr.unplace(); + } else { + boundingBoxes.add(newBoundingBox); + placed++; + newPlacementMap.put(curr, anchor); + System.out.println(" ** PLACED: " + placed + " " + anchor + " " + curr.getName() + + " " + curr.getAnchor().getTile().getSLR()); + curr = null; + searchDown = false; + } + } + } + if (!searchDown) { + gridX++; + } else { + gridY++; + } + } + } + + private void placeArray() { + List, String>> idealPlacementList = prepareArrayForPlacement(); + + t.stop().start("Place Instances"); + if (config.getOutputPlacementLocsFileName() != null) { + writePlacementLocsToFile(modules, config.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); + } + } + + if (config.getInputPlacementFileName() != null) { + placeInstancesWithManualPlacementFile(); + } else { + placeModuleInstancesAutomatically(idealPlacementList); + } + + if (config.getOutputPlacementFileName() != null) { + writePlacementToFile(getNewPlacementMap(), config.getOutputPlacementFileName()); + } + } + + private void finalizeStandaloneArray() { + if (config.getTopDesign() != null) { + throw new RuntimeException("Cannot call finalizeStandaloneArray on an array with a top level design"); + } + EDIFCell top = array.getTopEDIFCell(); + EDIFHierNet clkNet = array.getNetlist().getHierNetFromName(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(getKernelClockName()); + clk.connect(bufg, "O"); + Net clkIn = array.createNet(getKernelClockName() + "_in"); + clkIn.connect(bufg, "I"); + EDIFPort clkInPort = top.createPort(getKernelClockName(), EDIFDirection.INPUT, 1); + clkIn.getLogicalNet().createPortInst(clkInPort); + EDIFNet logClkNet = clk.getLogicalNet(); + for (EDIFCellInst inst : top.getCellInsts()) { + EDIFPort port = inst.getPort(getKernelClockName()); + 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, getKernelClockName(), config.getClockPeriod()); + array.setDesignOutOfContext(true); + array.setAutoIOBuffers(false); + } + + private static void unrouteStaticNets(Design design) { + Net gndNet = design.getNet(Net.GND_NET); + if (gndNet != null) { + gndNet.unroute(); + } + Net vccNet = design.getNet(Net.VCC_NET); + if (vccNet != null) { + vccNet.unroute(); + } + } + + private void createFlopHarnessForArray() { + t.stop().start("Create flop harness"); + // Automatically find bounding PBlock based on used Slices, DSPs, and BRAMs + Set usedSites = new HashSet<>(); + for (SiteInst siteInst : array.getSiteInsts()) { + if (siteInst.getName().contains(SiteInst.STATIC_SOURCE)) { + continue; + } + if (isSLICE(siteInst) || isBRAM(siteInst) || isDSP(siteInst)) { + usedSites.add(siteInst.getSite()); + } + } + PBlock pBlock = new PBlock(array.getDevice(), usedSites); + InlineFlopTools.createAndPlaceFlopsInlineOnTopPortsNearPins(array, getTopClockName(), pBlock); + } + + private void routeArray() { + if (config.isRouteClock() && !config.isRouteDesign()) { + t.stop().start("Route clock"); + Net clockNet = array.getNet(config.getTopClockName()); + DesignTools.makePhysNetNameConsistent(array, clockNet); + DesignTools.createPossiblePinsToStaticNets(array); + DesignTools.createMissingSitePinInsts(array, clockNet); + List pinsToRoute = clockNet.getPins(); + + PartialRouter.routeDesignPartialNonTimingDriven(array, pinsToRoute); + } else if (config.isRouteDesign()) { + t.stop().start("Route design"); + PartialCUFR.routeDesignWithUserDefinedArguments(array, new String[]{ + "--fixBoundingBox", + "--useUTurnNodes", + "--nonTimingDriven", + }); + } + } + + public void createArray() { + // Place the array + placeArray(); + + // Unroute conflicting nets + List unrouted = NetTools.unrouteNetsWithOverlappingNodes(getArray()); + if (!unrouted.isEmpty()) { + System.out.println("Found " + unrouted.size() + " overlapping nets, that were unrouted."); + } + + if (config.isSkipImpl() && config.getTopDesign() == null) { + finalizeStandaloneArray(); + } + + if (config.shouldUnrouteStaticNets()) { + unrouteStaticNets(array); + } + array.getNetlist().consolidateAllToWorkLibrary(); + array.flattenDesign(); + + if (config.isOutOfContext()) { + createFlopHarnessForArray(); + } + + routeArray(); + } + + private static Map, String> foldIdealPlacement(Map, String> placement, + Map newRowMap) { + if (newRowMap.isEmpty()) { + return placement; + } + // Map from (x,y) coordinate to the moduleInst name placed at that ideal (x,y) coordinate + Map, String> newPlacement = new HashMap<>(placement); + + // Check if row updates are unique + Set fromSet = new HashSet<>(); + Set toSet = new HashSet<>(); + for (Map.Entry rowUpdate : newRowMap.entrySet()) { + if (fromSet.contains(rowUpdate.getKey())) { + throw new RuntimeException("Non-unique source row when folding placement"); + } + if (toSet.contains(rowUpdate.getValue())) { + throw new RuntimeException("Non-unique destination row when folding placement"); + } + fromSet.add(rowUpdate.getKey()); + toSet.add(rowUpdate.getValue()); + } + if (!fromSet.containsAll(toSet)) { + throw new RuntimeException("Ideal placement folding provided with a non one-to-one mapping"); + } + + for (Map.Entry rowUpdate : newRowMap.entrySet()) { + int fromRow = rowUpdate.getKey(); + int toRow = rowUpdate.getValue(); + int currColumn = 0; + while (placement.containsKey(new Pair<>(currColumn, fromRow))) { + String cell = placement.get(new Pair<>(currColumn, fromRow)); + newPlacement.put(new Pair<>(currColumn, toRow), cell); + currColumn++; + } + // Remove rest of destination row if rows are not equal length + while (placement.containsKey(new Pair<>(currColumn, toRow))) { + newPlacement.remove(new Pair<>(currColumn, toRow)); + currColumn++; + } + } + return newPlacement; + } + + 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 + EDIFNet vcc = 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())); + vcc.createPortInst("CE", bufgce); + } 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 boundingBoxStraddlesClockRegion(RelocatableTileRectangle boundingBox) { + ClockRegion cr0 = boundingBox.getMaxColumnTile().getClockRegion(); + ClockRegion cr1 = boundingBox.getMinColumnTile().getClockRegion(); + ClockRegion cr2 = boundingBox.getMaxRowTile().getClockRegion(); + ClockRegion cr3 = boundingBox.getMinRowTile().getClockRegion(); + return !Stream.of(cr0, cr1, cr2, cr3).allMatch(cr0::equals); + } + + 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(); + } + + public static void main(String[] args) { + CodePerfTracker t = new CodePerfTracker(ArrayBuilder.class.getName()); + t.start("Init"); + + if (ArrayBuilderConfig.hasHelpArg(args)) { + ArrayBuilderConfig.printHelp(); + return; + } + + // Create config + ArrayBuilderConfig config = new ArrayBuilderConfig(args); + if (!config.isReuseResults()) { + config.setWorkDir("ArrayBuilder-" + FileTools.getTimeStamp().replace(" ", "-")); + } + + // Create array builder with config + ArrayBuilder ab = new ArrayBuilder(config, t); + ab.initializeArrayBuilder(); + + ab.createArray(); + + t.stop().start("Write DCP"); + ab.getArray().writeCheckpoint(ArrayBuilderConfig.getOutputName(args)); + t.stop().printSummary(); + } +} diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayBuilderConfig.java b/src/com/xilinx/rapidwright/design/tools/ArrayBuilderConfig.java new file mode 100644 index 0000000000..81899ca769 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilderConfig.java @@ -0,0 +1,480 @@ +/* + * + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Andrew Butt, 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 com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.device.Part; +import com.xilinx.rapidwright.device.PartNameTools; +import com.xilinx.rapidwright.edif.EDIFNetlist; +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 joptsimple.OptionParser; +import joptsimple.OptionSet; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A collection of customizable parameters for a {@link ArrayBuilder} Object. + * Modifications of default parameter values can be done by adding corresponding + * options with values to the arguments or by calling the applicable setter method. + */ +public class ArrayBuilderConfig { + + private Design kernelDesign; + + private Design topDesign; + + private double clkPeriod; + + private String kernelClkName; + + private String topClkName; + + private boolean skipImpl; + + private int instCountLimit; + + private String outputPlacementFileName; + + private String inputPlacementFileName; + + private String outputPlacementLocsFileName; + + private boolean outOfContext; + + private boolean exactPlacement; + + private boolean unrouteStaticNets; + + private boolean routeClock; + + private boolean routeDesign; + + private String sideMapFile; + + private String workDir; + + private boolean reuseResults; + + private Part part; + + private String[] pblockStrings; + + private String utilReport; + + private String shapesReport; + + private static final double DEFAULT_CLK_PERIOD_TARGET = 2.0; + + 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"); + 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 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 EXACT_PLACEMENT_OPTS = Collections.singletonList("exact-placement"); + 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 static final List UNROUTE_STATIC_NETS_OPTS = Collections.singletonList("unroute-static-nets"); + private static final List ROUTE_CLOCK_OPTS = Collections.singletonList("route-clock-only"); + private static final List ROUTE_DESIGN_OPTS = Collections.singletonList("route"); + private static final List SIDE_MAP_OPTS = Collections.singletonList("kernel-side-map"); + + private ArrayBuilderConfig() { + clkPeriod = DEFAULT_CLK_PERIOD_TARGET; + skipImpl = true; + instCountLimit = Integer.MAX_VALUE; + outOfContext = true; + exactPlacement = false; + unrouteStaticNets = false; + routeClock = true; + routeDesign = false; + reuseResults = false; + pblockStrings = null; + workDir = "ArrayBuilder-" + FileTools.getTimeStamp().replace(" ", "-"); + } + + public ArrayBuilderConfig(Design kernelDesign, Design topDesign) { + this(); + setKernelDesign(kernelDesign); + setTopDesign(topDesign); + } + + public ArrayBuilderConfig(String[] arguments) { + this(); + parseArguments(arguments); + } + + public static OptionParser createOptionParser() { + return new OptionParser() { + { + 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(); + 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(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(EXACT_PLACEMENT_OPTS, "Use exact module overlap calculation instead of the faster bounding-box method"); + acceptsAll(OUT_OF_CONTEXT_OPTS, "Specifies that the array will be compiled out of context"); + acceptsAll(UNROUTE_STATIC_NETS_OPTS, "Unroute static (GND/VCC) nets to potentially help with routability"); + acceptsAll(ROUTE_CLOCK_OPTS, "Route clock using RWRoute"); + acceptsAll(ROUTE_DESIGN_OPTS, "Route the built array using RWRoute"); + acceptsAll(SIDE_MAP_OPTS, "Provide a text file specifying which side of the pblock each " + + "top-level port routes to. Used to place array optimally for routability.").withRequiredArg(); + acceptsAll(HELP_OPTS, "Print this help message").forHelp(); + } + }; + } + + public static void printHelp() { + OptionParser p = createOptionParser(); + 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(); + } + } + + private void parseArguments(String[] arguments) { + OptionParser p = createOptionParser(); + OptionSet options = p.parse(arguments); + + setSkipImpl(options.has(SKIP_IMPL_OPTS.get(0))); + setOutOfContext(options.has(OUT_OF_CONTEXT_OPTS.get(0))); + setExactPlacement(options.has(EXACT_PLACEMENT_OPTS.get(0))); + setUnrouteStaticNets(options.has(UNROUTE_STATIC_NETS_OPTS.get(0))); + setRouteClock(options.has(ROUTE_CLOCK_OPTS.get(0))); + setRouteDesign(options.has(ROUTE_DESIGN_OPTS.get(0))); + setSideMapFile((String) options.valueOf(SIDE_MAP_OPTS.get(0))); + + if (options.has(PART_OPTS.get(0))) { + setPart(PartNameTools.getPart((String) options.valueOf(PART_OPTS.get(0)))); + } + + String kernelDesignPath; + if (options.has(KERNEL_DESIGN_OPTS.get(0))) { + kernelDesignPath = (String) options.valueOf(KERNEL_DESIGN_OPTS.get(0)); + } else { + throw new RuntimeException("No input design found. " + + "Please specify an input kernel (*.dcp or *.edf) using options " + + KERNEL_DESIGN_OPTS); + } + + Path inputFile = Paths.get(kernelDesignPath); + 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))); + setKernelDesign(Design.readCheckpoint(inputFile, companionEDIF, CodePerfTracker.SILENT)); + } else { + setKernelDesign(Design.readCheckpoint(inputFile)); + EDIFTools.removeVivadoBusPreventionAnnotations(getKernelDesign().getNetlist()); + if (!kernelDesign.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")) { + EDIFNetlist netlist = EDIFTools.readEdifFile(inputFile); + if (options.has(PART_OPTS.get(0))) { + EDIFTools.ensureCorrectPartInEDIF(netlist, getPart().toString()); + } + setKernelDesign(new Design(netlist)); + } + + 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(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(LIMIT_INSTS_OPTS.get(0))) { + setInstCountLimit(Integer.parseInt((String) options.valueOf(LIMIT_INSTS_OPTS.get(0)))); + } + + 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))); + } + + if (options.has(PBLOCK_OPTS.get(0))) { + String pblockString = (String) options.valueOf(PBLOCK_OPTS.get(0)); + setPBlockStrings(pblockString.split(";")); + } + + if (options.has(UTILIZATION_OPTS.get(0)) && options.has(SHAPES_OPTS.get(0))) { + setUtilReport((String) options.valueOf(UTILIZATION_OPTS.get(0))); + setShapesReport((String) options.valueOf(SHAPES_OPTS.get(0))); + } + + if (options.has(KERNEL_CLK_NAME_OPTS.get(0))) { + setKernelClockName(((String) options.valueOf(KERNEL_CLK_NAME_OPTS.get(0)))); + } + + if (options.has(TOP_CLK_NAME_OPTS.get(0))) { + setTopClockName(((String) options.valueOf(TOP_CLK_NAME_OPTS.get(0)))); + } + + if (options.has(REUSE_RESULTS_OPTS.get(0))) { + setWorkDir((String) options.valueOf(REUSE_RESULTS_OPTS.get(0))); + } + } + + public static boolean hasHelpArg(String[] arguments) { + OptionParser p = createOptionParser(); + OptionSet options = p.parse(arguments); + return options.has(HELP_OPTS.get(0)); + } + + public static String getOutputName(String[] args) { + OptionParser p = createOptionParser(); + OptionSet options = p.parse(args); + if (options.has(OUTPUT_DESIGN_OPTS.get(0))) { + return (String) options.valueOf(OUTPUT_DESIGN_OPTS.get(0)); + } + + return "array.dcp"; + } + + + public void setKernelDesign(Design kernelDesign) { + this.kernelDesign = kernelDesign; + } + + public Design getKernelDesign() { + return kernelDesign; + } + + public Design getTopDesign() { + return topDesign; + } + + public void setTopDesign(Design topDesign) { + this.topDesign = topDesign; + } + + public void setClockPeriod(double clkPeriod) { + this.clkPeriod = clkPeriod; + } + + public double getClockPeriod() { + return clkPeriod; + } + + public void setKernelClockName(String clkName) { + this.kernelClkName = clkName; + } + + public String getKernelClockName() { + return kernelClkName; + } + + public void setTopClockName(String clkName) { + this.topClkName = clkName; + } + + public String getTopClockName() { + return topClkName; + } + + public boolean isSkipImpl() { + return skipImpl; + } + + public void setSkipImpl(boolean skipImpl) { + this.skipImpl = skipImpl; + } + + public int getInstCountLimit() { + return instCountLimit; + } + + 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 isOutOfContext() { + return outOfContext; + } + + public void setOutOfContext(boolean outOfContext) { + this.outOfContext = outOfContext; + } + + public boolean isExactPlacement() { + return exactPlacement; + } + + public void setExactPlacement(boolean exactPlacement) { + this.exactPlacement = exactPlacement; + } + + public boolean shouldUnrouteStaticNets() { + return unrouteStaticNets; + } + + public void setUnrouteStaticNets(boolean unrouteStaticNets) { + this.unrouteStaticNets = unrouteStaticNets; + } + + public boolean isRouteClock() { + return routeClock; + } + + public void setRouteClock(boolean routeClock) { + this.routeClock = routeClock; + } + + public boolean isRouteDesign() { + return routeDesign; + } + + public void setRouteDesign(boolean routeDesign) { + this.routeDesign = routeDesign; + } + + public String getSideMapFile() { + return sideMapFile; + } + + public void setSideMapFile(String sideMapFile) { + this.sideMapFile = sideMapFile; + } + + public Part getPart() { + return part; + } + + public void setPart(Part part) { + this.part = part; + } + + public String[] getPBlockStrings() { + return pblockStrings; + } + + public void setPBlockStrings(String[] pblockStrings) { + this.pblockStrings = pblockStrings; + } + + public String getUtilReport() { + return utilReport; + } + + public void setUtilReport(String utilReport) { + this.utilReport = utilReport; + } + + public String getShapesReport() { + return shapesReport; + } + + public void setShapesReport(String shapesReport) { + this.shapesReport = shapesReport; + } + + public String getWorkDir() { + return workDir; + } + + public void setWorkDir(String workDir) { + this.workDir = workDir; + } + + public boolean isReuseResults() { + return reuseResults; + } + + public void setReuseResults(boolean reuseResults) { + this.reuseResults = reuseResults; + } +} diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayNetlistGraph.java b/src/com/xilinx/rapidwright/design/tools/ArrayNetlistGraph.java new file mode 100644 index 0000000000..3650a0dd0d --- /dev/null +++ b/src/com/xilinx/rapidwright/design/tools/ArrayNetlistGraph.java @@ -0,0 +1,439 @@ +/* + * + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Andrew Butt, 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 com.google.ortools.Loader; +import com.google.ortools.sat.CpModel; +import com.google.ortools.sat.CpSolver; +import com.google.ortools.sat.CpSolverStatus; +import com.google.ortools.sat.IntVar; +import com.google.ortools.sat.LinearExpr; +import com.google.ortools.sat.LinearExprBuilder; +import com.google.ortools.sat.Literal; +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.blocks.PBlockSide; +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFHierPortInst; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.edif.EDIFPort; +import com.xilinx.rapidwright.util.Pair; +import org.jgrapht.Graph; +import org.jgrapht.GraphPath; +import org.jgrapht.alg.cycle.CycleDetector; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.DefaultDirectedGraph; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.traverse.TopologicalOrderIterator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * A graph providing an abstract representation of a netlist comprised of blackbox cells. + * Used by ArrayBuilder to calculate an ideal placement for a netlist that minimizes distanced between + * nearest neighbors. + */ +public class ArrayNetlistGraph { + /** + * Graph edge that contains an orthogonal direction from the provided side map. + * Direction is used to pick a placement that improves routability. + */ + private static class NetlistEdge extends DefaultEdge { + private final PBlockSide direction; + + NetlistEdge(PBlockSide direction) { + this.direction = direction; + } + + public boolean noDirectionSpecified() { + return direction == null; + } + + public boolean isRight() { + return direction == PBlockSide.RIGHT; + } + + public boolean isLeft() { + return direction == PBlockSide.LEFT; + } + + public boolean isBelow() { + return direction == PBlockSide.BOTTOM; + } + + public boolean isAbove() { + return direction == PBlockSide.TOP; + } + + @Override + public String toString() { + return "(" + getSource() + " : " + getTarget() + ", " + this.direction + ")"; + } + } + private Graph graph; + private Map, Boolean> directionMap; + + public ArrayNetlistGraph() { + graph = new DefaultDirectedGraph<>(NetlistEdge.class); + } + + public ArrayNetlistGraph(Design array, List modules) { + this(array, modules, null); + } + + public ArrayNetlistGraph(Design array, List modules, Map sideMap) { + this(); + EDIFNetlist netlist = array.getNetlist(); + for (String module : modules) { + addVertex(module); + } + + for (String module : modules) { + EDIFHierCellInst cellInst = netlist.getHierCellInstFromName(module); + for (EDIFHierPortInst portInst : cellInst.getHierPortInsts()) { + if (portInst.isOutput()) { + for (EDIFHierPortInst netPortInst : portInst.getHierarchicalNet().getPortInsts()) { + if (!netPortInst.equals(portInst) && netPortInst.getCellType() != null) { + EDIFHierCellInst destCellInst = netPortInst.getFullHierarchicalInst(); + if (destCellInst != null && containsNode(destCellInst.getFullHierarchicalInstName())) { + PBlockSide pBlockSide = sideMap == null ? null : sideMap.get(portInst.getPortInst().getPort()); + + addEdge(cellInst.getFullHierarchicalInstName(), + destCellInst.getFullHierarchicalInstName(), + pBlockSide); + } + } + } + } + } + } + } + + public void addVertex(String name) { + graph.addVertex(name); + } + + public boolean containsNode(String name) { + return graph.containsVertex(name); + } + + public void addEdge(String from, String to, PBlockSide direction) { + graph.addEdge(from, to, new NetlistEdge(direction)); + } + + public boolean isAcyclic() { + CycleDetector cycleDetector = new CycleDetector<>(graph); + return !cycleDetector.detectCycles(); + } + + public Iterator getTopologicalOrderIterator() { + return new TopologicalOrderIterator<>(graph); + } + + public Map, String> getGreedyPlacementGrid() { + Map, String> placementMap = new HashMap<>(); + Map> reversePlacementMap = new HashMap<>(); + Map candidateMap = new HashMap<>(); + Iterator iterator = getTopologicalOrderIterator(); + DijkstraShortestPath dsp = new DijkstraShortestPath<>(graph); + String topLeftNode = iterator.next(); + placementMap.put(new Pair<>(0, 0), topLeftNode); + reversePlacementMap.put(topLeftNode, new Pair<>(0, 0)); + for (NetlistEdge edge : graph.outgoingEdgesOf(topLeftNode)) { + String node = graph.getEdgeTarget(edge); + candidateMap.put(node, 1); + } + + NetlistEdge extraConstraintEdge = graph.outgoingEdgesOf(topLeftNode).iterator().next(); + if (extraConstraintEdge.isRight() || extraConstraintEdge.isBelow()) { + // Add additional constraint based on the sideMap + String extraConstraintNode = graph.getEdgeTarget(extraConstraintEdge); + candidateMap.remove(extraConstraintNode); + Pair extraConstraintPlacement; + if (extraConstraintEdge.isRight()) { + extraConstraintPlacement = new Pair<>(1, 0); + } else { + extraConstraintPlacement = new Pair<>(0, 1); + } + placementMap.put(extraConstraintPlacement, extraConstraintNode); + reversePlacementMap.put(extraConstraintNode, extraConstraintPlacement); + for (NetlistEdge edge : graph.outgoingEdgesOf(extraConstraintNode)) { + String targetNode = graph.getEdgeTarget(edge); + int count = candidateMap.computeIfAbsent(targetNode, (n) -> 0); + candidateMap.put(targetNode, count + 1); + } + } + while (!candidateMap.isEmpty()) { + List sortedCandidates = candidateMap.entrySet().stream() + .sorted((e1, e2) -> { + if (e1.getValue() == e2.getValue()) { + // Tie-break of shorted path distance + GraphPath shortestPathE1 = dsp.getPath(topLeftNode, e1.getKey()); + GraphPath shortestPathE2 = dsp.getPath(topLeftNode, e2.getKey()); + return shortestPathE1.getLength() - shortestPathE2.getLength(); + } + return e2.getValue().compareTo(e1.getValue()); + }) + .map(Map.Entry::getKey).collect(Collectors.toList()); + String node = sortedCandidates.get(0); + candidateMap.remove(node); + for (NetlistEdge edge : graph.outgoingEdgesOf(node)) { + String targetNode = graph.getEdgeTarget(edge); + int count = candidateMap.computeIfAbsent(targetNode, (n) -> 0); + candidateMap.put(targetNode, count + 1); + } + Set inEdges = graph.incomingEdgesOf(node); + List inNeighbors = new ArrayList<>(); + for (NetlistEdge e : inEdges) { + inNeighbors.add(graph.getEdgeSource(e)); + } + if (inNeighbors.size() > 3) { + throw new RuntimeException("Greedy placement does not work for given netlist"); + } + List> inNeighborPlacements = new ArrayList<>(); + for (String inNeighbor : inNeighbors) { + inNeighborPlacements.add(reversePlacementMap.get(inNeighbor)); + } + inNeighborPlacements = inNeighborPlacements.stream().sorted( + (p1, p2) -> { + if (p1.getSecond().equals(p2.getSecond())) { + return p1.getFirst() - p2.getFirst(); + } + return p1.getSecond() - p2.getSecond(); + }).collect(Collectors.toList()); + List> validPlacements = new ArrayList<>(); + if (inNeighbors.size() == 1) { + Pair neighborPlacement = inNeighborPlacements.get(0); + validPlacements.add(new Pair<>(neighborPlacement.getFirst() + 1, neighborPlacement.getSecond())); + validPlacements.add(new Pair<>(neighborPlacement.getFirst(), neighborPlacement.getSecond() + 1)); + } else if (inNeighbors.size() == 2) { + int x = inNeighborPlacements.get(0).getFirst(); + int y = inNeighborPlacements.get(1).getSecond(); + validPlacements.add(new Pair<>(x, y)); + } else { + throw new RuntimeException("Not yet implemented, try using OR-tools based placement"); + } + Pair placement = null; + for (Pair location : validPlacements) { + if (!placementMap.containsKey(location)) { + placement = location; + } + } + if (placement == null) { + throw new RuntimeException("Could not find valid greedy placement for cell: " + node); + } + placementMap.put(placement, node); + reversePlacementMap.put(node, placement); + } + + for (int y = 0; y < graph.vertexSet().size(); y++) { + for (int x = 0; x < graph.vertexSet().size(); x++) { + if (placementMap.containsKey(new Pair<>(x, y))) { + System.out.println("Placed " + placementMap.get(new Pair<>(x, y)) + " at (" + x + ", " + y + ")"); + } + } + } + + return placementMap; + } + + public Map, String> getOptimalPlacementGrid(int width, int height) { + Map, String> placementMap = new HashMap<>(); + int numNodes = graph.vertexSet().size(); + Map numToNameMap = new HashMap<>(); + Map nameToNumMap = new HashMap<>(); + + int i = 0; + for (String v : graph.vertexSet()) { + numToNameMap.put(i, v); + nameToNumMap.put(v, i); + i++; + } + + Loader.loadNativeLibraries(); + CpModel model = new CpModel(); + Literal[][][] placements = new Literal[numNodes][width][height]; + for (int n = 0; n < numNodes; n++) { + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + placements[n][x][y] = model.newBoolVar("placement_n" + n + "x" + x + "y" + y); + } + } + } + + // At most one node can be placed at each grid location + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + List nodes = new ArrayList<>(); + for (int n = 0; n < numNodes; n++) { + nodes.add(placements[n][x][y]); + } + model.addAtMostOne(nodes); + } + } + + // Every node must be placed at exactly one location + for (int n = 0; n < numNodes; n++) { + List locations = new ArrayList<>(); + for (int x = 0; x < width; x++) { + locations.addAll(Arrays.asList(placements[n][x]).subList(0, height)); + } + model.addExactlyOne(locations); + } + + // Add auxiliary variables for x and y placement + IntVar[] xPlacement = new IntVar[numNodes]; + IntVar[] yPlacement = new IntVar[numNodes]; + for (int n = 0; n < numNodes; n++) { + xPlacement[n] = model.newIntVar(0, width, "x_loc_n" + n); + yPlacement[n] = model.newIntVar(0, height, "y_loc_n" + n); + LinearExprBuilder xExpr = LinearExpr.newBuilder(); + LinearExprBuilder yExpr = LinearExpr.newBuilder(); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + xExpr.addTerm(placements[n][x][y], x); + yExpr.addTerm(placements[n][x][y], y); + } + } + model.addEquality(xPlacement[n], xExpr); + model.addEquality(yPlacement[n], yExpr); + } + + // Add auxiliary variables for x and y distance between connected nodes + List xDistVars = new ArrayList<>(); + List yDistVars = new ArrayList<>(); + for (String v : graph.vertexSet()) { + Set outEdges = graph.outgoingEdgesOf(v); + int sourceNum = nameToNumMap.get(v); + for (NetlistEdge e : outEdges) { + String edgeTarget = graph.getEdgeTarget(e); + int targetNum = nameToNumMap.get(edgeTarget); + + // x distance variable + IntVar xDistVar = model.newIntVar(0, width, "x_dist_" + v + "_n" + sourceNum + "_to_" + edgeTarget + "_n" + targetNum); + xDistVars.add(xDistVar); + IntVar sourceXVar = xPlacement[sourceNum]; + IntVar targetXVar = xPlacement[targetNum]; + + // Adding both of these constraints is equivalent to xDistVar = abs(sourceX - targetX) + LinearExprBuilder sourceMinusTargetX = LinearExpr.newBuilder(); + sourceMinusTargetX.addTerm(sourceXVar, 1); + sourceMinusTargetX.addTerm(targetXVar, -1); + model.addGreaterOrEqual(xDistVar, sourceMinusTargetX); + + LinearExprBuilder targetMinusSourceX = LinearExpr.newBuilder(); + targetMinusSourceX.addTerm(targetXVar, 1); + targetMinusSourceX.addTerm(sourceXVar, -1); + model.addGreaterOrEqual(xDistVar, targetMinusSourceX); + + // y distance variable + IntVar yDistVar = model.newIntVar(0, width, "y_dist_" + v + "_n" + sourceNum + "_to_" + edgeTarget + "_n" + targetNum); + yDistVars.add(yDistVar); + IntVar sourceYVar = yPlacement[sourceNum]; + IntVar targetYVar = yPlacement[targetNum]; + + // Adding both of these constraints is equivalent to xDistVar = abs(sourceX - targetX) + LinearExprBuilder sourceMinusTargetY = LinearExpr.newBuilder(); + sourceMinusTargetY.addTerm(sourceYVar, 1); + sourceMinusTargetY.addTerm(targetYVar, -1); + model.addGreaterOrEqual(yDistVar, sourceMinusTargetY); + + LinearExprBuilder targetMinusSourceY = LinearExpr.newBuilder(); + targetMinusSourceY.addTerm(targetYVar, 1); + targetMinusSourceY.addTerm(sourceYVar, -1); + model.addGreaterOrEqual(yDistVar, targetMinusSourceY); + + // Neighbors must be adjacent + LinearExprBuilder xDistPlusYDist = LinearExpr.newBuilder(); + xDistPlusYDist.addSum(new IntVar[]{xDistVar, yDistVar}); + model.addLessOrEqual(xDistPlusYDist, 1); + model.addLessOrEqual(xDistVar, 3); + model.addLessOrEqual(yDistVar, 3); + } + } + + // Place the anchor in the top left corner + String anchor = getTopologicalOrderIterator().next(); + int anchorNum = nameToNumMap.get(anchor); + model.addAssumption(placements[anchorNum][0][0]); + + NetlistEdge extraConstraintEdge = graph.outgoingEdgesOf(anchor).iterator().next(); + if (extraConstraintEdge.isRight() || extraConstraintEdge.isBelow()) { + // Add additional constraint based on the sideMap + String extraConstraintNode = graph.getEdgeTarget(extraConstraintEdge); + int extraConstraintNum = nameToNumMap.get(extraConstraintNode); + if (extraConstraintEdge.isRight()) { + model.addAssumption(placements[extraConstraintNum][1][0]); + } else { + model.addAssumption(placements[extraConstraintNum][0][1]); + } + } + + IntVar maxXDistVar = model.newIntVar(0, width, "max_x_dist"); + for (IntVar xDistVar : xDistVars) { + model.addGreaterOrEqual(maxXDistVar, xDistVar); + } + IntVar maxYDistVar = model.newIntVar(0, width, "max_y_dist"); + for (IntVar yDistVar : yDistVars) { + model.addGreaterOrEqual(maxYDistVar, yDistVar); + } + LinearExprBuilder obj = LinearExpr.newBuilder(); + obj.add(maxXDistVar); + obj.add(maxYDistVar); + model.minimize(obj); + + CpSolver solver = new CpSolver(); + CpSolverStatus status = solver.solve(model); + + if (status == CpSolverStatus.FEASIBLE || status == CpSolverStatus.OPTIMAL) { + System.out.println("Solution: " + status); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + for (int n = 0; n < numNodes; n++) { + if (solver.booleanValue(placements[n][x][y])) { + System.out.println("Placed " + numToNameMap.get(n) + " at (" + x + ", " + y + ")"); + placementMap.put(new Pair<>(x, y), numToNameMap.get(n)); + break; + } + } + } + } + } else { + throw new RuntimeException("Failed to find optimal placement grid, solver returned status: " + status); + } + + return placementMap; + } + + @Override + public String toString() { + return graph.toString(); + } +} diff --git a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java index 231dc65d16..03003a9cc7 100644 --- a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java +++ b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java @@ -23,12 +23,15 @@ package com.xilinx.rapidwright.design.tools; import java.util.ArrayList; +import java.util.Arrays; 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.stream.Collectors; +import java.util.Map.Entry; import com.xilinx.rapidwright.design.Cell; import com.xilinx.rapidwright.design.Design; @@ -38,25 +41,23 @@ 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.design.blocks.PBlockSide; +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.FileTools; import com.xilinx.rapidwright.util.Pair; import com.xilinx.rapidwright.util.StringTools; +import static com.xilinx.rapidwright.util.Utils.isCLB; + /** * 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 { @@ -66,49 +67,173 @@ 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 for 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 clkNet Name of the clock net to use for 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); + } + + private static Site shiftSiteToSide(Device device, Site site, PBlock pblock, PBlockSide side) { + Tile topLeftTile = pblock.getTopLeftTile(); + Tile bottomRightTile = pblock.getBottomRightTile(); + Tile siteTile = site.getTile(); + int pBlockTop = topLeftTile.getRow(); + int pBlockLeft = topLeftTile.getColumn(); + int pBlockRight = bottomRightTile.getColumn(); + int pBlockBottom = bottomRightTile.getRow(); + int siteTileCol = siteTile.getColumn(); + int siteTileRow = siteTile.getRow(); + + Tile shiftedTile; + if (side == PBlockSide.TOP) { + // Shift tile up + shiftedTile = shiftTileUntilSlice(device, pBlockTop, siteTileCol, true, true); + } else if (side == PBlockSide.LEFT) { + // Shift tile left + shiftedTile = shiftTileUntilSlice(device, siteTileRow, pBlockLeft, false, true); + } else if (side == PBlockSide.RIGHT) { + // Shift tile right + shiftedTile = shiftTileUntilSlice(device, siteTileRow, pBlockRight, false, false); + } else { + // Shift tile down + shiftedTile = shiftTileUntilSlice(device, pBlockBottom, siteTileCol, true, false); + } + Site shiftedSite = site.getCorrespondingSite(site.getSiteTypeEnum(), shiftedTile); + if (shiftedSite == null) { + // Can't find a site on the edge of the PBlock, return the original site + return site; + } + + return shiftedSite; + } + + /** + * 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 for the flops + * @param keepOut The pblock used to contain the kernel and the added flops will + * not be placed inside this area. + * @param portSideMap Map from ports to side of the pblock the flop should be placed on + */ + public static void createAndPlacePortFlopsOnSide(Design design, String clkNet, PBlock keepOut, + Map portSideMap) { + assert (design.getSiteInsts().isEmpty()); + Site start = keepOut.getAllSites("SLICE").iterator().next(); // TODO this is a bit wasteful + boolean exclude = true; + + EDIFHierNet clk = design.getNetlist().getHierNetFromName(clkNet); + + Set siteInstsToRoute = new HashSet<>(); + + for (Entry entry : portSideMap.entrySet()) { + EDIFPort port = entry.getKey(); + PBlockSide side = entry.getValue(); + if (port.getName().equals(clkNet)) { + continue; + } + Site shiftedSite = shiftSiteToSide(design.getDevice(), start, keepOut, side); + for (int i : port.getBitBlastedIndices()) { + EDIFPortInst inst = port.getInternalPortInstFromIndex(i); + if (allLeavesAreIBUF(design, inst)) { + continue; + } + + Iterator siteItr = ECOPlacementHelper.spiralOutFrom(shiftedSite, keepOut, exclude).iterator(); + siteItr.next(); // Skip the first site, as we are suggesting one inside the pblock + Pair loc = nextAvailPlacement(design, siteItr, null); + if (loc == null) { + throw new RuntimeException("Failed to find valid placement location for flip-flop"); + } + Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } + } + for (SiteInst si : siteInstsToRoute) { + si.routeSite(); + } + } + + /** + * 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 for 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) { 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.getBitBlastedIndices()) { - EDIFPortInst inst = port.getInternalPortInstFromIndex(i); - Pair loc = nextAvailPlacement(design, siteItr); + for (int i : port.getBitBlastedIndices()) { + EDIFPortInst inst = port.getInternalPortInstFromIndex(i); + if (allLeavesAreIBUF(design, inst)) { + continue; + } + + if (centroidPlacement) { + netCentroidFlipFlopPlacement(design, inst, keepOut, clk, siteInstsToRoute); + } else { + Iterator siteItr = ECOPlacementHelper.spiralOutFrom(start, keepOut, exclude).iterator(); + siteItr.next(); // Skip the first site, as we are suggesting one inside the pblock + Pair loc = nextAvailPlacement(design, siteItr, null); 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) { @@ -116,20 +241,147 @@ public static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clk } } - private static Pair nextAvailPlacement(Design design, Iterator itr) { + 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 Tile shiftTileUntilSlice(Device device, int row, int col, boolean shiftRow, boolean negativeShift) { + int offset = negativeShift ? -1 : 1; + Tile shiftedTile = null; + while (shiftedTile == null || !isCLB(shiftedTile.getTileTypeEnum())) { + int shiftedRow = shiftRow ? row + offset : row; + int shiftedCol = !shiftRow ? col + offset : col; + if (shiftRow && (shiftedRow < 0 || shiftedRow > device.getRows())) { + shiftedTile = null; + break; + } + if (!shiftRow && (shiftedCol < 0 || shiftedCol > device.getColumns())) { + shiftedTile = null; + break; + } + shiftedTile = device.getTile(shiftedRow, shiftedCol); + offset = negativeShift ? offset - 1 : offset + 1; + } + return shiftedTile; + } + + private static Site getSiteOnPBlockEdgeClosestToSite(Device device, Site site, PBlock pblock) { + Tile topLeftTile = pblock.getTopLeftTile(); + Tile bottomRightTile = pblock.getBottomRightTile(); + Tile siteTile = site.getTile(); + int pBlockTop = topLeftTile.getRow(); + int pBlockLeft = topLeftTile.getColumn(); + int pBlockRight = bottomRightTile.getColumn(); + int pBlockBottom = bottomRightTile.getRow(); + int siteTileCol = siteTile.getColumn(); + int siteTileRow = siteTile.getRow(); + int topDist = siteTileRow - pBlockTop; + int bottomDist = pBlockBottom - siteTileRow; + int leftDist = siteTileCol - pBlockLeft; + int rightDist = pBlockRight - siteTileCol; + + Tile shiftedTile = null; + if (topDist <= leftDist && topDist <= rightDist && topDist <= bottomDist) { + // Shift tile up + shiftedTile = shiftTileUntilSlice(device, pBlockTop, siteTileCol, true, true); + } else if (leftDist <= topDist && leftDist <= rightDist && leftDist <= bottomDist) { + // Shift tile left + shiftedTile = shiftTileUntilSlice(device, siteTileRow, pBlockLeft, false, true); + } else if (rightDist <= topDist && rightDist <= leftDist && rightDist <= bottomDist) { + // Shift tile right + shiftedTile = shiftTileUntilSlice(device, siteTileRow, pBlockRight, false, false); + } else { + // Shift tile down + shiftedTile = shiftTileUntilSlice(device, pBlockBottom, siteTileCol, true, false); + } + Site shiftedSite = site.getCorrespondingSite(site.getSiteTypeEnum(), shiftedTile); + if (shiftedSite == null) { + // Can't find a site on the edge of the PBlock, return the original site + return site; + } + + return shiftedSite; + } + + 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); + Site shiftedCentroid = getSiteOnPBlockEdgeClosestToSite(design.getDevice(), centroid, keepOut); + Iterator siteItr = ECOPlacementHelper.spiralOutFrom(shiftedCentroid, keepOut, true).iterator(); + if (keepOut.containsTile(shiftedCentroid.getTile())) { + siteItr.next(); + } + Pair loc = nextAvailPlacement(design, siteItr, shiftedCentroid.getTile().getSLR()); + Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } + } + + private static Pair nextAvailPlacement(Design design, Iterator itr, SLR slr) { while (itr.hasNext()) { Site curr = itr.next(); + if (slr != null && curr.getTile().getSLR() != slr) { + continue; + } 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) { + /** + * Add flip-flop inline on the specified top-level port of an out-of-context design. + * This is useful for out-of-context kernels so that after the flop has been + * placed, the router is forced to route connections of the port to the flop. + * This can help alleviate congestion when the kernels are placed/relocated in context. + * + * @param design The design to modify + * @param portInst The port to place an inline flip-flop on + * @param loc A pair of the site and BEL to place the flip-flop at + * @param clk The clock net to use for the inline flip-flop + */ + public static Cell createAndPlaceFlopInlineOnTopPortInst(Design design, EDIFPortInst portInst, Pair loc, + EDIFHierNet clk) { String name = portInst.getFullName() + INLINE_SUFFIX; Cell flop = design.createAndPlaceCell(design.getTopEDIFCell(), name, Unisim.FDRE, loc.getFirst(), loc.getSecond()); @@ -137,7 +389,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 +415,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 +426,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 = getStaticPins(design); for (EDIFCellInst inst : design.getTopEDIFCell().getCellInsts()) { if (inst.getName().endsWith(INLINE_SUFFIX)) { Cell flop = design.getCell(inst.getName()); @@ -176,6 +434,9 @@ public static void removeInlineFlops(Design design) { // Assume we only placed one flop per SiteInst siteInstToRemove.add(si); for (SitePinInst pin : si.getSitePinInsts()) { + if (pin.getNet().isGNDNet()) { + continue; + } pinsToRemove.computeIfAbsent(pin.getNet(), p -> new HashSet<>()).add(pin); } for (String staticPin : staticPins) { @@ -188,18 +449,18 @@ public static void removeInlineFlops(Design design) { } } - for (SiteInst si : siteInstToRemove) { - design.removeSiteInst(si); - } + 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 for (String pin : ctrlPins) { EDIFPortInst p = c.getPortInst(pin); - p.getNet().removePortInst(p); + if (p != null && p.getNet() != null) { + p.getNet().removePortInst(p); + } } // Merge 'D' sources and 'Q' sinks, restore original net EDIFPortInst d = c.getPortInst("D"); @@ -232,15 +493,76 @@ public static void removeInlineFlops(Design design) { design.removeCell(c.getName()); } + for (SiteInst si : siteInstToRemove) { + boolean isStaticSource = si.getSitePinInsts().stream() + .anyMatch((p) -> p.getNet() != null && p.getNet().isGNDNet() && p.isOutPin()); + if (!isStaticSource) { + design.removeSiteInst(si); + } + } } + private static final Map staticPinsMap; + static { + staticPinsMap = new HashMap<>(); + staticPinsMap.put(Series.Series7, new String[]{"CE", "SR"}); + staticPinsMap.put(Series.UltraScale, new String[]{"CKEN1", "CKEN2", "CKEN3", "CKEN4", "SRST1", "SRST2"}); + staticPinsMap.put(Series.UltraScalePlus, staticPinsMap.get(Series.UltraScale)); + staticPinsMap.put(Series.Versal, new String[]{"CKEN1", "CKEN2", "CKEN3", "CKEN4", "RST"}); + } + + private static String[] getStaticPins(Design design) { + String[] staticPins = staticPinsMap.get(design.getSeries()); + if (staticPins == null) { + throw new RuntimeException("Unsupported device series for removing inline flops"); + } + return staticPins; + } + + /** + * Parses the PBlock side map into a map from EDIFPorts to PBlockSide enums. The input file should be made up of + * some number of lines where each line contains a port name regex and a PBlockSide separated by a space. An + * example file: + *
+     * example_inputs.* TOP
+     * reset LEFT
+     * example_outputs.* BOTTOM
+     * 
+ * + * @param netlist The netlist that the side map will be created for. + * @param filename The name of the input side map file. + * @return A map from EDIFPort to the PBlockSide the inline flop should be placed on. + */ + public static Map parseSideMap(EDIFNetlist netlist, String filename) { + Map externalRoutabilitySideMap = new HashMap<>(); + List lines = FileTools.getLinesFromTextFile(filename); + + for (String line : lines) { + String[] splitLine = line.split("\\s+"); + String portRegex = splitLine[0]; + String pblockSide = splitLine[1].toUpperCase(); + for (EDIFPort port : netlist.getTopCell().getPorts()) { + if (port.getBusName().matches(portRegex) || + port.getName().matches("\\" + EDIFTools.VIVADO_PRESERVE_PORT_INTERFACE + portRegex)) { + if (externalRoutabilitySideMap.containsKey(port)) { + throw new RuntimeException("Port " + port + " matches multiple expressions in side map"); + } + PBlockSide side = PBlockSide.valueOf(pblockSide); + externalRoutabilitySideMap.put(port, side); + } + } + } + return externalRoutabilitySideMap; + } + + 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 +570,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/design/xdc/UnsupportedConstraintElement.java b/src/com/xilinx/rapidwright/design/xdc/UnsupportedConstraintElement.java index 5e7aa1b2bb..1e8068659d 100644 --- a/src/com/xilinx/rapidwright/design/xdc/UnsupportedConstraintElement.java +++ b/src/com/xilinx/rapidwright/design/xdc/UnsupportedConstraintElement.java @@ -279,7 +279,6 @@ private static void absorbIntoCells(EdifCellLookup lookup, List void absorbIntoCells(EdifCellLookup lookup, List void absorbIntoCells(EdifCellLookup lookup, List 1) { - System.out.println("chose arbitrary wildcard match for "+currLevel+": "+child); - } } else { break; } @@ -317,15 +312,11 @@ private static void absorbIntoCells(EdifCellLookup lookup, 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/EDIFCell.java b/src/com/xilinx/rapidwright/edif/EDIFCell.java index 23ee508458..e361e8ae6d 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 */ @@ -806,5 +806,33 @@ public int getNonHierInstantiationCount() { public boolean isUniquified() { return getNonHierInstantiationCount() <= 1; } + + /** + * 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. + */ + 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) { + otherPort = otherPorts.get(EDIFTools.VIVADO_PRESERVE_PORT_INTERFACE + + port.getBusName(true)); + } + if (otherPort == null || port.getWidth() != otherPort.getWidth() + || port.getDirection() != otherPort.getDirection()) { + return false; + } + } + return true; + } } diff --git a/src/com/xilinx/rapidwright/edif/EDIFTools.java b/src/com/xilinx/rapidwright/edif/EDIFTools.java index 564419f574..69e973cca7 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())); + } } } diff --git a/src/com/xilinx/rapidwright/util/PerformanceExplorer.java b/src/com/xilinx/rapidwright/util/PerformanceExplorer.java index 35cfccfb48..adc668930b 100644 --- a/src/com/xilinx/rapidwright/util/PerformanceExplorer.java +++ b/src/com/xilinx/rapidwright/util/PerformanceExplorer.java @@ -28,19 +28,22 @@ 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; -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.NetTools; import com.xilinx.rapidwright.design.blocks.PBlock; +import com.xilinx.rapidwright.design.tools.InlineFlopTools; +import com.xilinx.rapidwright.design.blocks.PBlockSide; +import com.xilinx.rapidwright.edif.EDIFPort; +import com.xilinx.rapidwright.edif.EDIFTools; import joptsimple.OptionParser; import joptsimple.OptionSet; @@ -52,8 +55,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; @@ -77,10 +83,14 @@ 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; + private boolean getBestPerPBlock; + private ArrayList placerDirectives; private ArrayList routerDirectives; @@ -95,6 +105,14 @@ public class PerformanceExplorer { private String vivadoPath = DEFAULT_VIVADO; + private boolean reusePreviousResults; + + private boolean ensureExternalRoutability; + + private String externalRoutabilitySideFile; + + Map externalRoutabilitySideMap = null; + public PerformanceExplorer(Design d, String testDir, String clkName, double targetPeriod) { init(d, testDir, clkName, targetPeriod, null); } @@ -262,23 +280,71 @@ 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 String getExternalRoutabilitySideFile() { + return externalRoutabilitySideFile; + } + + public void setExternalRoutabilitySideFile(String externalRoutabilitySideFile) { + this.externalRoutabilitySideFile = externalRoutabilitySideFile; + } + + 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) { + 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(); 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+"]"); } } - 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); @@ -296,6 +362,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()) { @@ -306,16 +391,8 @@ 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(); @@ -327,38 +404,66 @@ public void explorePerformance() { int pb = 0; int jobsStarted = 0; - int maxConcurrentJobs = JobQueue.isLSFAvailable() ? JobQueue.MAX_LSF_CONCURRENT_JOBS : JobQueue.MAX_LOCAL_CONCURRENT_JOBS; + 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(); + + String pblockDcpName = dcpName; + if (ensureExternalRoutability()) { + EDIFTools.removeVivadoBusPreventionAnnotations(design.getNetlist()); + design.getNetlist().resetParentNetMap(); + if (getExternalRoutabilitySideFile() == null) { + InlineFlopTools.createAndPlaceFlopsInlineOnTopPortsArbitrarily(design, clkName, pblock); + } else { + Map sideMap = + InlineFlopTools.parseSideMap(design.getNetlist(), + getExternalRoutabilitySideFile()); + InlineFlopTools.createAndPlacePortFlopsOnSide(design, clkName, pblock, sideMap); + } + EDIFTools.ensurePreservedInterfaceVivado(design.getNetlist()); + pblockDcpName = runDirectory + File.separator + "pblock" + pb + "_" + INITIAL_DCP_NAME; + design.writeCheckpoint(pblockDcpName); + } + for (PlacerDirective p : getPlacerDirectives()) { for (RouterDirective r : getRouterDirectives()) { for (double c : getClockUncertaintyValues()) { 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 encryptedTcl = null; + boolean encrypted = design.getNetlist().hasEncryptedCells(); + 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; - 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 +471,80 @@ 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; + } + + 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); + } + + System.out.println("Best result found at: " + bestPath); + Design d = Design.readCheckpoint(bestPath + File.separator + "routed.dcp"); + + if (ensureExternalRoutability()) { + InlineFlopTools.removeInlineFlops(d); + NetTools.unrouteTopLevelNetsThatLeavePBlock(d, getPBlock(pblockNum)); + } + + EDIFTools.ensurePreservedInterfaceVivado(d.getNetlist()); + d.writeCheckpoint(runDirectory + File.separator + pblock + "_best.dcp"); + } + } private static final String INPUT_DCP_OPT = "i"; private static final String PBLOCK_FILE_OPT = "b"; @@ -383,6 +562,9 @@ public void explorePerformance() { 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 @@ -404,6 +586,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).withOptionalArg().describedAs("Ensure all I/O are routable outside the pblock. Optionally provide a text file specifying which side of the pblock each top-level port should route to."); + 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(); }}; @@ -425,7 +610,6 @@ private static void printHelp(OptionParser p) { return; } - public static String printNS(double num) { return df.format(num); } @@ -445,6 +629,7 @@ public static void main(String[] args) { String runDir = opts.hasArgument(RUN_DIR_OPT) ? (String) opts.valueOf(RUN_DIR_OPT) : System.getProperty("user.dir"); Design d = Design.readCheckpoint(dcpInputName); + EDIFTools.ensurePreservedInterfaceVivado(d.getNetlist()); PerformanceExplorer pe = new PerformanceExplorer(d, runDir, clkName, targetPeriod); if (opts.hasArgument(MAX_CONCURRENT_JOBS_OPT)) { @@ -467,7 +652,12 @@ 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(ENSURE_EXT_ROUTABILITY)) { + pe.setExternalRoutabilitySideFile((String) opts.valueOf(ENSURE_EXT_ROUTABILITY)); + } if (opts.hasArgument(PBLOCK_FILE_OPT)) { String fileName = (String) opts.valueOf(PBLOCK_FILE_OPT); @@ -491,5 +681,9 @@ public static void main(String[] args) { } pe.explorePerformance(); + + if (pe.getBestPerPBlock) { + pe.getBestDesignPerPBlock(); + } } } diff --git a/src/com/xilinx/rapidwright/util/Utils.java b/src/com/xilinx/rapidwright/util/Utils.java index 69c243409c..d66014d5bd 100644 --- a/src/com/xilinx/rapidwright/util/Utils.java +++ b/src/com/xilinx/rapidwright/util/Utils.java @@ -393,7 +393,8 @@ public static boolean isNOC(SiteTypeEnum s) { lockedSiteTypes = EnumSet.of( SiteTypeEnum.CONFIG_SITE, - SiteTypeEnum.BUFG + SiteTypeEnum.BUFG, + SiteTypeEnum.BUFGCE ); sliceTypes = EnumSet.of( diff --git a/src/com/xilinx/rapidwright/util/VivadoTools.java b/src/com/xilinx/rapidwright/util/VivadoTools.java index b5cbce7509..08ecb3ae24 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. @@ -209,7 +219,6 @@ public static List writeBitstream(Path dcp, Path bitFile, boolean hasEnc .getPath("vivadoToolsWorkdir" + FileTools.getUniqueProcessAndHostID()); File workdirHandle = new File(workdir.toString()); workdirHandle.mkdirs(); - final Path outputLog = workdir.resolve("outputLog.log"); StringBuilder sb = new StringBuilder(); sb.append(createTclDCPLoadCommand(dcp, hasEncryptedIP)); @@ -282,15 +291,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; } @@ -474,6 +477,31 @@ 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()); + } + /** * Open a DCP in Vivado and write a new DCP. * diff --git a/test/src/com/xilinx/rapidwright/design/TestNetTools.java b/test/src/com/xilinx/rapidwright/design/TestNetTools.java index a1469be9cc..4628cff561 100644 --- a/test/src/com/xilinx/rapidwright/design/TestNetTools.java +++ b/test/src/com/xilinx/rapidwright/design/TestNetTools.java @@ -21,18 +21,26 @@ */ package com.xilinx.rapidwright.design; +import java.nio.file.Path; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; +import com.xilinx.rapidwright.design.blocks.PBlock; 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.EDIFCell; +import com.xilinx.rapidwright.edif.EDIFDirection; import com.xilinx.rapidwright.edif.EDIFHierNet; +import com.xilinx.rapidwright.edif.EDIFNet; +import com.xilinx.rapidwright.edif.EDIFPort; +import com.xilinx.rapidwright.util.CodeGenerator; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -42,15 +50,15 @@ public class TestNetTools { /** * Tests the method NetTools.isGlobalClock(Net net). * 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. + * 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. - * + * * All Versal/Ultrascale+/Series-7 designs under the RapidWrightDCP path are available to be checked, except for the following: * picoblaze4_ooc_X6Y60_X6Y65_X10Y60_X10Y65.dcp The source cell bufgce_inst of this design is not placed yet. * picoblaze_ooc_X10Y235_unreadable_edif.dcp This design would call vivado to generate an readable EDIF file. - * - * @param pathAndlobalClockNames Use ' ' as the delimiter to split this string, where the first element is the path to the DCP file, + * + * @param pathAndGlobalClockNames Use ' ' as the delimiter to split this string, where the first element is the path to the DCP file, * and the remaining elements are the names of the global clock nets reported by Vivado. */ @ParameterizedTest @@ -73,7 +81,7 @@ public class TestNetTools { "picoblaze_partial.dcp clk", // "reduce_or_routed_7overlaps.dcp", // "testCopyImplementation.dcp", - + // UltraScale // "microblazeAndILA_3pblocks.dcp base_mb_i/clk_wiz_1/inst/clk_out1 base_mb_i/clk_wiz_1/inst/clkfbout_buf_base_mb_clk_wiz_1_0 base_mb_i/mdm_1/U0/No_Dbg_Reg_Access.BUFG_DRCK/Dbg_Clk_31 dbg_hub/inst/BSCANID.u_xsdbm_id/itck_i", "microblazeAndILA_3pblocks_2024.1.dcp base_mb_i/clk_wiz_1/inst/clk_out1 base_mb_i/clk_wiz_1/inst/clkfbout_buf_base_mb_clk_wiz_1_0 base_mb_i/mdm_1/U0/No_Dbg_Reg_Access.BUFG_DRCK/Dbg_Clk_31 dbg_hub/inst/BSCANID.u_xsdbm_id/itck_i", @@ -93,8 +101,8 @@ public class TestNetTools { // "routethru_luts.dcp", // "routethru_pip.dcp", }) - public void testisGlobalClock(String pathAndlobalClockNames) { - String[] tmpList = pathAndlobalClockNames.split(" "); + public void testisGlobalClock(String pathAndGlobalClockNames) { + String[] tmpList = pathAndGlobalClockNames.split(" "); String path = tmpList[0]; HashSet globalClockNamesFromVivado = new HashSet<>(); for (int i = 1; i < tmpList.length; i++) { @@ -258,4 +266,62 @@ public void testFindClockRootVRoute() { ClockRegion clockRoot = NetTools.findClockRootVRoute(clkNet).getTile().getClockRegion(); Assertions.assertEquals(clockRoot, design.getDevice().getClockRegion(1, 1)); } + + @Test + public void testUnrouteTopLevelNetsThatLeavePBlock() { + Design design = new Design("pblock_unroute_test", "xcv80-lsva4737-2MHP-e-S"); + design.setDesignOutOfContext(true); + design.setAutoIOBuffers(false); + EDIFCell topCell = design.getNetlist().getTopCell(); + EDIFPort clkPort = topCell.createPort("clk", EDIFDirection.INPUT, 1); + EDIFNet clkNet = topCell.createNet("clk"); + clkNet.createPortInst(clkPort); + String[] pips = {"CLE_E_CORE_X31Y804/CLE_E_CORE.CLE_SLICEL_TOP_0_AQ_PIN->>CLE_SLICEL_TOP_0_AQ", + "INT_X31Y804/INT.LOGIC_OUTS_W1->INT_NODE_SDQ_ATOM_16_INT_OUT1", + "INT_X31Y804/INT.INT_NODE_SDQ_ATOM_16_INT_OUT1->>OUT_NN2_W_BEG0", + "INT_X31Y806/INT.IN_NN2_W_END0->INT_NODE_SDQ_ATOM_59_INT_OUT0", + "INT_X31Y806/INT.INT_NODE_SDQ_ATOM_59_INT_OUT0->>OUT_WW2_W_BEG0", + "INT_X30Y806/INT.IN_WW2_W_END0->INT_NODE_SDQ_ATOM_123_INT_OUT1", + "INT_X30Y806/INT.INT_NODE_SDQ_ATOM_123_INT_OUT1->>OUT_NN1_W_BEG2", + "INT_X30Y807/INT.IN_NN1_W_END2->INT_NODE_SDQ_ATOM_62_INT_OUT0", + "INT_X30Y807/INT.INT_NODE_SDQ_ATOM_62_INT_OUT0->>INT_SDQ_RED_ATOM_24_INT_OUT0", + "INT_X30Y807/INT.INT_SDQ_RED_ATOM_24_INT_OUT0->INT_NODE_IMUX_ATOM_65_INT_OUT0", + "INT_X30Y807/INT.INT_NODE_IMUX_ATOM_65_INT_OUT0->>BOUNCE_E0", + "CLE_W_CORE_X30Y807/CLE_W_CORE.CLE_SLICEL_TOP_0_AX->>CLE_SLICEL_TOP_0_AX_PIN", + "INT_X31Y806/INT.INT_NODE_SDQ_ATOM_59_INT_OUT0->>INT_SDQ_RED_ATOM_16_INT_OUT0", + "INT_X31Y806/INT.INT_SDQ_RED_ATOM_16_INT_OUT0->INT_NODE_SDQ_ATOM_11_INT_OUT1", + "INT_X31Y806/INT.INT_NODE_SDQ_ATOM_11_INT_OUT1->>OUT_EE1_E_BEG2", + "INT_X32Y806/INT.IN_EE1_E_END2->INT_NODE_IMUX_ATOM_97_INT_OUT0", + "INT_X32Y806/INT.INT_NODE_IMUX_ATOM_97_INT_OUT0->>BOUNCE_W0", + "CLE_E_CORE_X32Y806/CLE_E_CORE.CLE_SLICEL_TOP_0_AX->>CLE_SLICEL_TOP_0_AX_PIN"}; + + + Net net = CodeGenerator.createTestNet(design, "test_net", pips); + EDIFHierNet logicalClkNet = new EDIFHierNet(design.getNetlist().getTopHierCellInst(), clkPort.getInternalNet()); + Net clk = design.createNet(logicalClkNet); + Cell flop0 = design.createAndPlaceCell(design.getTopEDIFCell(), "flop0", + Unisim.FDRE, "SLICE_X98Y800/AFF"); + net.connect(flop0, "Q"); + design.getGndNet().connect(flop0, "R"); + design.getVccNet().connect(flop0, "CE"); + clk.connect(flop0, "C"); + + Cell flop1 = design.createAndPlaceCell(design.getTopEDIFCell(), "flop1", + Unisim.FDRE, "SLICE_X96Y803/AFF"); + net.connect(flop1, "D"); + design.getGndNet().connect(flop1, "R"); + design.getVccNet().connect(flop1, "CE"); + clk.connect(flop1, "C"); + + EDIFPort outPort = topCell.createPort("out", EDIFDirection.OUTPUT, 1); + EDIFNet testNet = design.getNetlist().getHierNetFromName("test_net").getNet(); + testNet.createPortInst(outPort); + PBlock pblock = new PBlock(design.getDevice(), "IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 " + + "DSP_X0Y398:DSP_X1Y405 " + "DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 " + "SLICE_X92Y796:SLICE_X99Y811"); + + List unroutedNets = NetTools.unrouteTopLevelNetsThatLeavePBlock(design, pblock); + Assertions.assertEquals(1, unroutedNets.size()); + Assertions.assertEquals("test_net", unroutedNets.get(0).getName()); + Assertions.assertEquals(0, net.getPIPs().size()); + } } 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 0000000000..ac163d0ead --- /dev/null +++ b/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java @@ -0,0 +1,53 @@ +/* + * + * 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; + +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)); + + } +} diff --git a/test/src/com/xilinx/rapidwright/design/tools/TestInlineFlopTools.java b/test/src/com/xilinx/rapidwright/design/tools/TestInlineFlopTools.java new file mode 100644 index 0000000000..7118a57496 --- /dev/null +++ b/test/src/com/xilinx/rapidwright/design/tools/TestInlineFlopTools.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Andrew Butt, AMD Research and Advanced 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 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.Unisim; +import com.xilinx.rapidwright.design.blocks.PBlock; +import com.xilinx.rapidwright.design.xdc.ConstraintTools; +import com.xilinx.rapidwright.device.BEL; +import com.xilinx.rapidwright.device.SLR; +import com.xilinx.rapidwright.device.Site; +import com.xilinx.rapidwright.eco.ECOPlacementHelper; +import com.xilinx.rapidwright.edif.EDIFCell; +import com.xilinx.rapidwright.edif.EDIFDirection; +import com.xilinx.rapidwright.edif.EDIFHierNet; +import com.xilinx.rapidwright.edif.EDIFPort; +import com.xilinx.rapidwright.edif.EDIFPortInst; +import com.xilinx.rapidwright.support.RapidWrightDCP; +import com.xilinx.rapidwright.util.Pair; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class TestInlineFlopTools { + + private static Pair nextAvailPlacement(Design design, Iterator itr, SLR slr, String bel) { + while (itr.hasNext()) { + Site curr = itr.next(); + if (slr != null && curr.getTile().getSLR() != slr) { + continue; + } + SiteInst candidate = design.getSiteInstFromSite(curr); + if (candidate == null) { + return new Pair<>(curr, curr.getBEL(bel)); + } + } + return null; + } + + private static void createAndPlaceFlopInlineAtSpecificBEL(Design design, String clkNet, EDIFPort port, String bel, + PBlock keepOut) { + Site start = keepOut.getAllSites("SLICE").iterator().next(); + boolean exclude = true; + + EDIFHierNet clk = design.getNetlist().getHierNetFromName(clkNet); + + Set siteInstsToRoute = new HashSet<>(); + + for (int i : port.getBitBlastedIndices()) { + EDIFPortInst inst = port.getInternalPortInstFromIndex(i); + + Iterator siteItr = ECOPlacementHelper.spiralOutFrom(start, keepOut, exclude).iterator(); + siteItr.next(); // Skip the first site, as we are suggesting one inside the pblock + Pair loc = nextAvailPlacement(design, siteItr, null, bel); + Cell flop = InlineFlopTools.createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } + for (SiteInst si : siteInstsToRoute) { + si.routeSite(); + } + } + + @ParameterizedTest + @CsvSource({ + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,AFF,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,AFF2,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,BFF,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,BFF2,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,CFF,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,CFF2,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,DFF,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,DFF2,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,EFF,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,EFF2,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,FFF,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,FFF2,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,GFF,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,GFF2,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,HFF,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcv80-lsva4737-2MHP-e-S,SLICE_X96Y803/AFF,HFF2,IRI_QUAD_X58Y3212:IRI_QUAD_X59Y3275 DSP_X0Y398:DSP_X1Y405 DSP58_CPLX_X0Y398:DSP58_CPLX_X0Y405 SLICE_X92Y796:SLICE_X99Y811", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,AFF,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,AFF2,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,BFF,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,BFF2,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,CFF,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,CFF2,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,DFF,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,DFF2,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,EFF,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,EFF2,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,FFF,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,FFF2,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,GFF,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,GFF2,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,HFF,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xcvu3p-ffvc1517-2-i,SLICE_X14Y237/AFF,HFF2,RAMB36_X1Y47:RAMB36_X1Y47 RAMB18_X1Y94:RAMB18_X1Y95 SLICE_X13Y235:SLICE_X16Y239", + "xc7a200tsbg484-1,SLICE_X60Y124/AFF,AFF,RAMB36_X3Y22:RAMB36_X3Y27 RAMB18_X3Y44:RAMB18_X3Y55 DSP48_X2Y44:DSP48_X3Y55 SLICE_X46Y110:SLICE_X67Y139", + "xc7a200tsbg484-1,SLICE_X60Y124/AFF,A5FF,RAMB36_X3Y22:RAMB36_X3Y27 RAMB18_X3Y44:RAMB18_X3Y55 DSP48_X2Y44:DSP48_X3Y55 SLICE_X46Y110:SLICE_X67Y139", + "xc7a200tsbg484-1,SLICE_X60Y124/AFF,BFF,RAMB36_X3Y22:RAMB36_X3Y27 RAMB18_X3Y44:RAMB18_X3Y55 DSP48_X2Y44:DSP48_X3Y55 SLICE_X46Y110:SLICE_X67Y139", + "xc7a200tsbg484-1,SLICE_X60Y124/AFF,B5FF,RAMB36_X3Y22:RAMB36_X3Y27 RAMB18_X3Y44:RAMB18_X3Y55 DSP48_X2Y44:DSP48_X3Y55 SLICE_X46Y110:SLICE_X67Y139", + "xc7a200tsbg484-1,SLICE_X60Y124/AFF,CFF,RAMB36_X3Y22:RAMB36_X3Y27 RAMB18_X3Y44:RAMB18_X3Y55 DSP48_X2Y44:DSP48_X3Y55 SLICE_X46Y110:SLICE_X67Y139", + "xc7a200tsbg484-1,SLICE_X60Y124/AFF,C5FF,RAMB36_X3Y22:RAMB36_X3Y27 RAMB18_X3Y44:RAMB18_X3Y55 DSP48_X2Y44:DSP48_X3Y55 SLICE_X46Y110:SLICE_X67Y139", + "xc7a200tsbg484-1,SLICE_X60Y124/AFF,DFF,RAMB36_X3Y22:RAMB36_X3Y27 RAMB18_X3Y44:RAMB18_X3Y55 DSP48_X2Y44:DSP48_X3Y55 SLICE_X46Y110:SLICE_X67Y139", + "xc7a200tsbg484-1,SLICE_X60Y124/AFF,D5FF,RAMB36_X3Y22:RAMB36_X3Y27 RAMB18_X3Y44:RAMB18_X3Y55 DSP48_X2Y44:DSP48_X3Y55 SLICE_X46Y110:SLICE_X67Y139", + }) + + public void testRemoveInlineFlops(String partName, String ffBel, String harnessBel, String pblockRange) { + Design design = new Design("inline_flops", partName); + design.setDesignOutOfContext(true); + design.setAutoIOBuffers(false); + EDIFCell topCell = design.getNetlist().getTopCell(); + // Create clock port and net + EDIFPort clkPort = topCell.createPort("clk", EDIFDirection.INPUT, 1); + Net clk = design.createNet("clk"); + EDIFHierNet logicalClkNet = clk.getLogicalHierNet(); + logicalClkNet.getNet().createPortInst(clkPort); + + // Create in port and net + EDIFPort inPort = topCell.createPort("in", EDIFDirection.INPUT, 1); + Net inNet = design.createNet("in"); + EDIFHierNet logicalInNet = inNet.getLogicalHierNet(); + logicalInNet.getNet().createPortInst(inPort); + + // Create out port and net + EDIFPort outPort = topCell.createPort("out", EDIFDirection.OUTPUT, 1); + Net outNet = design.createNet("out"); + EDIFHierNet logicalOutNet = outNet.getLogicalHierNet(); + logicalOutNet.getNet().createPortInst(outPort); + + Cell flop0 = design.createAndPlaceCell(design.getTopEDIFCell(), "flop0", + Unisim.FDRE, ffBel); + inNet.connect(flop0, "D"); + outNet.connect(flop0, "Q"); + design.getGndNet().connect(flop0, "R"); + design.getVccNet().connect(flop0, "CE"); + clk.connect(flop0, "C"); + + PBlock pblock = new PBlock(design.getDevice(), pblockRange); + createAndPlaceFlopInlineAtSpecificBEL(design, "clk", inPort, harnessBel, pblock); + design.getNetlist().resetParentNetMap(); + createAndPlaceFlopInlineAtSpecificBEL(design, "clk", outPort, harnessBel, pblock); + InlineFlopTools.removeInlineFlops(design); + for (Cell c : design.getCells()) { + Assertions.assertFalse(c.getName().endsWith(InlineFlopTools.INLINE_SUFFIX)); + } + Assertions.assertEquals(1, design.getVccNet().getSinkPins().size()); + } + + @Test + public void testArbitraryInlineFlopPlacement() { + Design design = RapidWrightDCP.loadDCP("PicoBlazeArray/pblock0.dcp"); + Map pblocks = ConstraintTools.getPBlocksFromXDC(design); + PBlock pblock = pblocks.get("pe_pblock_1"); + design.unrouteDesign(); + design.unplaceDesign(); + InlineFlopTools.createAndPlaceFlopsInlineOnTopPortsArbitrarily(design, "clk", pblock); + int count = 0; + for (Cell c : design.getCells()) { + if (c.isPlaced()) { + count++; + Assertions.assertTrue(c.getName().contains(InlineFlopTools.INLINE_SUFFIX)); + Assertions.assertFalse(c.getName().contains("clk")); + } + } + Assertions.assertEquals(65, count); + } + + @Test + public void testCentroidInlineFlopPlacement() { + Design design = RapidWrightDCP.loadDCP("PicoBlazeArray/pblock0.dcp"); + Map pblocks = ConstraintTools.getPBlocksFromXDC(design); + PBlock pblock = pblocks.get("pe_pblock_1"); + InlineFlopTools.createAndPlaceFlopsInlineOnTopPortsNearPins(design, "clk", pblock); + int count = 0; + for (Cell c : design.getCells()) { + if (c.isPlaced()) { + if (c.getName().contains(InlineFlopTools.INLINE_SUFFIX)) { + count++; + Assertions.assertFalse(c.getName().contains("clk")); + } + } + } + Assertions.assertEquals(65, count); + } +} diff --git a/test/src/com/xilinx/rapidwright/eco/TestECOTools.java b/test/src/com/xilinx/rapidwright/eco/TestECOTools.java index 24515cfcd2..dbc11cb3e8 100644 --- a/test/src/com/xilinx/rapidwright/eco/TestECOTools.java +++ b/test/src/com/xilinx/rapidwright/eco/TestECOTools.java @@ -1185,5 +1185,4 @@ public void testConnectNetWithoutSource() { VivadoToolsHelper.assertFullyRouted(design); } - } From 84016ea42929e850af4c7ce86c7ca5d7be351e5b Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Wed, 3 Jun 2026 15:26:25 -0600 Subject: [PATCH 4/7] rc4 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 dfd397f529..ad4c741932 100644 --- a/.classpath +++ b/.classpath @@ -33,9 +33,9 @@ - + - + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76bdde45c1..daa163910c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: pull_request: env: - RAPIDWRIGHT_VERSION: v2025.2.2-rc3-beta + RAPIDWRIGHT_VERSION: v2025.2.2-rc4-beta jobs: From 9d5357ba0cff1bcbe0933763a101c62bf3b2df77 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 4 Jun 2026 17:58:34 -0600 Subject: [PATCH 5/7] [Params] Adds parameters to control how readable EDIF is extracted when DCP is loaded (#1374) * Add parameter to select directory where unencrypted EDIF inside DCPs are extracted Signed-off-by: Chris Lavin * Add a threshold parameter. Signed-off-by: Chris Lavin * Aid in debugging Signed-off-by: Chris Lavin * Avoids trying to precompute hightest base wire index Signed-off-by: Chris Lavin * Better multi-thread handling Signed-off-by: Chris Lavin * Reverting RouteNodeGraph changes Signed-off-by: Chris Lavin * rc5 Signed-off-by: Chris Lavin --------- Signed-off-by: Chris Lavin --- .classpath | 4 +- .github/workflows/build.yml | 2 +- src/com/xilinx/rapidwright/util/Job.java | 4 +- src/com/xilinx/rapidwright/util/Params.java | 64 +++++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/.classpath b/.classpath index ad4c741932..e75be3b04b 100644 --- a/.classpath +++ b/.classpath @@ -33,9 +33,9 @@ - + - + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index daa163910c..3d1767ccd6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: pull_request: env: - RAPIDWRIGHT_VERSION: v2025.2.2-rc4-beta + RAPIDWRIGHT_VERSION: v2025.2.2-rc5-beta jobs: diff --git a/src/com/xilinx/rapidwright/util/Job.java b/src/com/xilinx/rapidwright/util/Job.java index 4cba6a0236..46739d25b0 100644 --- a/src/com/xilinx/rapidwright/util/Job.java +++ b/src/com/xilinx/rapidwright/util/Job.java @@ -57,6 +57,8 @@ public abstract class Job { public static final String DEFAULT_COMMAND_LOG_FILE = DEFAULT_COMMAND_NAME + DEFAULT_LOG_EXTENSION; + public static final int DEFAULT_LOG_LINES = 80; + public abstract long launchJob(); public abstract JobState getJobState(); @@ -151,7 +153,7 @@ public Optional> getLastLogLines() { String logFileName = getLogFilename(); if (new File(logFileName).exists()) { ArrayList lines = FileTools.getLinesFromTextFile(logFileName); - int start = lines.size() >= 8 ? lines.size()-8 : 0; + int start = lines.size() >= DEFAULT_LOG_LINES ? lines.size()-DEFAULT_LOG_LINES : 0; return Optional.of(IntStream.range(start, lines.size()).mapToObj(lines::get).collect(Collectors.toList())); } return Optional.empty(); diff --git a/src/com/xilinx/rapidwright/util/Params.java b/src/com/xilinx/rapidwright/util/Params.java index 76b4cd9546..15a6547441 100644 --- a/src/com/xilinx/rapidwright/util/Params.java +++ b/src/com/xilinx/rapidwright/util/Params.java @@ -40,6 +40,21 @@ public class Params { public static String RW_COPY_EDNS_ON_DCP_WRITE_NAME = "RW_COPY_EDNS_ON_DCP_WRITE"; + /** + * Directory where RapidWright should temporarily extract readable EDIF files + * embedded inside DCPs before parsing. This can be set either as an environment + * variable or JVM system property. If unset, RapidWright will try the DCP's + * directory first and then the JVM temp directory. + */ + public static String RW_DCP_EDIF_TEMP_DIR_NAME = "RW_DCP_EDIF_TEMP_DIR"; + + /** + * Minimum uncompressed EDIF size in bytes where RapidWright should extract a + * readable EDIF embedded inside a DCP before parsing. If unset or negative, + * RapidWright uses the parallel EDIF parser's thread-count heuristic. + */ + public static String RW_DCP_EDIF_EXTRACT_THRESHOLD_BYTES_NAME = "RW_DCP_EDIF_EXTRACT_THRESHOLD_BYTES"; + /** * Flag to have RapidWright decompress gzipped EDIF files to disk prior to * parsing. This is a tradeoff where pre-decompression improves runtime over the @@ -78,6 +93,20 @@ public class Params { */ public static boolean RW_COPY_EDNS_ON_DCP_WRITE = isParamSet(RW_COPY_EDNS_ON_DCP_WRITE_NAME); + /** + * Directory where RapidWright should temporarily extract readable EDIF files + * embedded inside DCPs before parsing, or null if unset. + */ + public static String RW_DCP_EDIF_TEMP_DIR = getParamValue(RW_DCP_EDIF_TEMP_DIR_NAME); + + /** + * Minimum uncompressed EDIF size in bytes where RapidWright should extract a + * readable EDIF embedded inside a DCP before parsing. A negative value means + * the threshold was not set explicitly. + */ + public static long RW_DCP_EDIF_EXTRACT_THRESHOLD_BYTES = + getParamOrDefaultLongSetting(RW_DCP_EDIF_EXTRACT_THRESHOLD_BYTES_NAME, -1L); + /** * Checks if the named RapidWright parameter is set via an environment variable * or by a JVM parameter of the same name. @@ -126,6 +155,27 @@ public static Integer getParamIntValue(String key) { return null; } + /** + * Gets the long value of the provided parameter name. + * + * @param key Name of the system parameter to get. + * @return The set long value of the parameter, or null if none was set. If + * the property is set to a value that is not a parsable long, a + * warning message is produced and returns null. + */ + public static Long getParamLongValue(String key) { + String envValue = getParamValue(key); + if (envValue != null) { + try { + return Long.parseLong(envValue); + } catch (NumberFormatException e) { + System.err.println("WARNING: Couldn't interpret the value '" + envValue + + "' from the parameter '" + key + "' as a long."); + } + } + return null; + } + /** * Gets the string value of the provided parameter name. * @@ -154,4 +204,18 @@ public static int getParamOrDefaultIntSetting(String key, int defaultValue) { return setValue == null ? defaultValue : setValue; } + /** + * Checks the parameter value of the provided key. If it is set, it returns the + * set value. Otherwise it will return the default value. + * + * @param key Name of the system parameter to check. + * @param defaultValue The default value to return if the parameter is not set. + * @return The system parameter value if is set, otherwise it returns + * defaultValue. + */ + public static long getParamOrDefaultLongSetting(String key, long defaultValue) { + Long setValue = getParamLongValue(key); + return setValue == null ? defaultValue : setValue; + } + } From 4df95fe510d4350d81ae797ea80b00e21448caa2 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 4 Jun 2026 19:47:15 -0600 Subject: [PATCH 6/7] 2025.2.2 jar 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 e75be3b04b..65d89aae10 100644 --- a/.classpath +++ b/.classpath @@ -33,9 +33,9 @@ - + - + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d1767ccd6..8db638810f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: pull_request: env: - RAPIDWRIGHT_VERSION: v2025.2.2-rc5-beta + RAPIDWRIGHT_VERSION: v2025.2.2-beta jobs: From 08c1abcdc0af5035e0febd8d52f2885f368f0e10 Mon Sep 17 00:00:00 2001 From: Chris Lavin Date: Thu, 4 Jun 2026 20:09:11 -0600 Subject: [PATCH 7/7] 2025.2.2 release notes Signed-off-by: Chris Lavin --- RELEASE_NOTES.TXT | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/RELEASE_NOTES.TXT b/RELEASE_NOTES.TXT index 26b6867ad8..b5401e84d3 100644 --- a/RELEASE_NOTES.TXT +++ b/RELEASE_NOTES.TXT @@ -1,3 +1,26 @@ +============= RapidWright 2025.2.2-beta released on 2026-06-04 ================ +Notes: + - [Params] Adds parameters to control how readable EDIF is extracted when DCP is loaded (#1374) + - Enable {dis,}connectNet() to operate on macro port insts (#1367) + - Array Builder Tool - Replicate Optimized Kernels in an Array (#1163) + - [RWRoute] On Versal, preserve node uphill of sink SPIs (#1369) + - [Clock Routing]Fix isse related to Versal clock routing to NoC sinks (#1370) + - Add test case for fixes to getCorrespondingTile when called on certain SLL tiles (#1360) + - Correctly handle NOC clock pins when routing (#1365) + - Fix IBUF creation for UltraScale+ (#1357) + - [DesignTools] Fix NPE in fullyUnplaceCell() (#1358) + - [PathExtractor] Fix unconnected static inputs (#1352) + - Auto extract readable EDIFs from DCPs to enable parallel parsing + - Fix create IOB on UltraScale+ + - Fixes to allow Vivado 2025.2 designs with NOC components to be loaded in RapidWright + - Fixes for site routing + - Adds several missing IDCodes for UltraScale+ parts + - Fix SLL module relocation for Versal + +API Additions: + - com.xilinx.rapidwright.design.noc.NOCDesign "public void clearSolution()" + + ============= RapidWright 2025.2.1-beta released on 2026-02-18 ================ Notes: - Fixes Issues with PathExtractor#1350