diff --git a/src/com/xilinx/rapidwright/design/DesignTools.java b/src/com/xilinx/rapidwright/design/DesignTools.java index f1d85936d..7e5c3cb7a 100644 --- a/src/com/xilinx/rapidwright/design/DesignTools.java +++ b/src/com/xilinx/rapidwright/design/DesignTools.java @@ -2000,7 +2000,7 @@ public static boolean stampPlacement(Design design, Module stamp, Map action) { SiteInst si = pin.getSiteInst(); diff --git a/src/com/xilinx/rapidwright/rwroute/Connection.java b/src/com/xilinx/rapidwright/rwroute/Connection.java index cd98c0c53..9f7d629cf 100644 --- a/src/com/xilinx/rapidwright/rwroute/Connection.java +++ b/src/com/xilinx/rapidwright/rwroute/Connection.java @@ -25,8 +25,10 @@ package com.xilinx.rapidwright.rwroute; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import com.xilinx.rapidwright.device.IntentCode; import com.xilinx.rapidwright.design.Net; import com.xilinx.rapidwright.design.SitePinInst; import com.xilinx.rapidwright.device.Node; @@ -49,7 +51,7 @@ public class Connection implements Comparable{ private RouteNode sourceRnode; private RouteNode altSourceRnode; private RouteNode sinkRnode; - private RouteNode altSinkRnode; + private List altSinkRnodes; /** * true to indicate the source and the sink are connected through dedicated resources, * such as the carry chain connections and connections between cascaded BRAMs. @@ -294,13 +296,25 @@ public RouteNode getSinkRnode() { return sinkRnode; } - public void setSinkRnode(RouteNode childRnode) { - sinkRnode = childRnode; + public void setSinkRnode(RouteNode sinkRnode) { + this.sinkRnode = sinkRnode; } - public RouteNode getAltSinkRnode() { return altSinkRnode; } + public List getAltSinkRnodes() { + return altSinkRnodes == null ? Collections.emptyList() : altSinkRnodes; + } - public void setAltSinkRnode(RouteNode childRnode) { altSinkRnode = childRnode; } + public void addAltSinkRnode(RouteNode sinkRnode) { + if (altSinkRnodes == null) { + altSinkRnodes = new ArrayList<>(1); + } else { + assert(!altSinkRnodes.contains(sinkRnode)); + } + assert(sinkRnode.getType() == RouteNodeType.PINFEED_I || + // Can be a WIRE if node is not exclusive a sink + sinkRnode.getType() == RouteNodeType.WIRE); + altSinkRnodes.add(sinkRnode); + } public short getXMinBB() { return xMinBB; @@ -467,10 +481,30 @@ public String toString() { return s.toString(); } - public void setTarget(boolean target) { - sinkRnode.setTarget(target); - if (altSinkRnode != null) { - altSinkRnode.setTarget(target); + public void setAllTargets(boolean target) { + if (sinkRnode.countConnectionsOfUser(netWrapper) == 0 || + sinkRnode.getNode().getIntentCode() == IntentCode.NODE_PINBOUNCE) { + // Since this connection will have been ripped up, only mark a node + // as a target if it's not already used by this net. + // This prevents -- for the case where the same net needs to be routed + // to the same LUT more than once -- the illegal case of the same + // physical pin servicing more than one logical pin + sinkRnode.setTarget(target); + } else { + assert(altSinkRnodes != null && !altSinkRnodes.isEmpty()); + } + if (altSinkRnodes != null) { + for (RouteNode rnode : altSinkRnodes) { + // Same condition as above: only allow this as an alternate sink + // if it's not already in use by the current net to prevent the case + // where the same physical pin services more than one logical pin + if (rnode.countConnectionsOfUser(netWrapper) == 0 || + // Except if it is not a PINFEED_I + rnode.getType() != RouteNodeType.PINFEED_I) { + assert(rnode.getNode().getIntentCode() != IntentCode.NODE_PINBOUNCE); + rnode.setTarget(target); + } + } } } } diff --git a/src/com/xilinx/rapidwright/rwroute/PartialRouter.java b/src/com/xilinx/rapidwright/rwroute/PartialRouter.java index 6fbc8fa5d..5ad489efa 100644 --- a/src/com/xilinx/rapidwright/rwroute/PartialRouter.java +++ b/src/com/xilinx/rapidwright/rwroute/PartialRouter.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -353,17 +354,19 @@ protected void determineRoutingTargets() { // (it's possible for another connection to use a bounce node, but now that node is // needed as a site pin) for (Connection connection : netWrapper.getConnections()) { - for (RouteNode rnode : Arrays.asList(connection.getSinkRnode(), connection.getAltSinkRnode())) { + Consumer action = (rnode) -> { if (rnode == null) { - continue; + return; } RouteNode prev = rnode.getPrev(); if (prev == null) { - continue; + return; } stashedPrev.put(rnode, prev); rnode.clearPrev(); - } + }; + action.accept(connection.getSinkRnode()); + connection.getAltSinkRnodes().forEach(action); } // Create all nodes used by this net and set its previous pointer so that: @@ -395,7 +398,20 @@ protected void determineRoutingTargets() { RouteNode sinkRnode = connection.getSinkRnode(); assert(sourceRnode.getType() == RouteNodeType.PINFEED_O); assert(sinkRnode.getType() == RouteNodeType.PINFEED_I); + + // Even though this connection is not expected to have any routing yet, + // perform a rip up anyway in order to release any exclusive sinks + // ahead of finishRouteConnection() + assert(connection.getRnodes().isEmpty()); + connection.getSink().setRouted(false); + ripUp(connection); + finishRouteConnection(connection, sinkRnode); + if (!connection.getSink().isRouted() && connection.getAltSinkRnodes().isEmpty()) { + // Undo what ripUp() did for this connection which has a single exclusive sink + sinkRnode.incrementUser(connection.getNetWrapper()); + sinkRnode.updatePresentCongestionCost(presentCongestionFactor); + } } // Restore prev to avoid assertions firing @@ -631,7 +647,20 @@ protected void unpreserveNet(Net net) { RouteNode sinkRnode = connection.getSinkRnode(); assert(sourceRnode.getType() == RouteNodeType.PINFEED_O); assert(sinkRnode.getType() == RouteNodeType.PINFEED_I); + + // Even though this connection is not expected to have any routing yet, + // perform a rip up anyway in order to release any exclusive sinks + // ahead of finishRouteConnection() + assert(connection.getRnodes().isEmpty()); + connection.getSink().setRouted(false); + ripUp(connection); + finishRouteConnection(connection, sinkRnode); + if (!connection.getSink().isRouted() && connection.getAltSinkRnodes().isEmpty()) { + // Undo what ripUp() did for this connection which has a single exclusive sink + sinkRnode.incrementUser(connection.getNetWrapper()); + sinkRnode.updatePresentCongestionCost(presentCongestionFactor); + } } netToPins.put(net, net.getSinkPins()); diff --git a/src/com/xilinx/rapidwright/rwroute/RWRoute.java b/src/com/xilinx/rapidwright/rwroute/RWRoute.java index 0dc41acb2..85bddb3c2 100644 --- a/src/com/xilinx/rapidwright/rwroute/RWRoute.java +++ b/src/com/xilinx/rapidwright/rwroute/RWRoute.java @@ -41,18 +41,22 @@ import java.util.function.Function; import java.util.stream.Collectors; +import com.xilinx.rapidwright.design.Cell; import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.DesignTools; import com.xilinx.rapidwright.design.Net; import com.xilinx.rapidwright.design.NetType; import com.xilinx.rapidwright.design.SiteInst; import com.xilinx.rapidwright.design.SitePinInst; +import com.xilinx.rapidwright.design.tools.LUTTools; +import com.xilinx.rapidwright.device.BEL; import com.xilinx.rapidwright.device.IntentCode; import com.xilinx.rapidwright.device.Node; import com.xilinx.rapidwright.device.PIP; import com.xilinx.rapidwright.device.Part; import com.xilinx.rapidwright.device.Series; -import com.xilinx.rapidwright.device.Tile; +import com.xilinx.rapidwright.device.Site; +import com.xilinx.rapidwright.device.SitePin; import com.xilinx.rapidwright.device.TileTypeEnum; import com.xilinx.rapidwright.edif.EDIFHierNet; import com.xilinx.rapidwright.interchange.Interchange; @@ -112,6 +116,8 @@ public class RWRoute{ private float timingWeight; /** 1 - timingWeight */ private float oneMinusTimingWeight; + /** Flag for whether LUT pin swaps are to be considered */ + protected boolean lutPinSwapping; /** The current routing iteration */ protected int routeIteration; @@ -221,6 +227,7 @@ protected void initialize() { rnodesCreatedThisIteration = 0; routethruHelper = new RouteThruHelper(design.getDevice()); presentCongestionFactor = config.getInitialPresentCongestionFactor(); + lutPinSwapping = config.getLutPinSwapping(); routerTimer.createRuntimeTracker("determine route targets", "Initialization").start(); determineRoutingTargets(); @@ -282,6 +289,14 @@ protected TimingManager createTimingManager(ClkRouteTiming clkTiming, Collection protected void determineRoutingTargets() { categorizeNets(); + // Since createNetWrapperAndConnections() both creates the primary sink node and + // computes alternate sinks (e.g. for LUT pin swaps), it is possible that + // an alternate sink for one net later becomes an exclusive sink for another net. + // Examine for all connections for this case and remove such alternate sinks. + for (Connection connection : indirectConnections) { + connection.getAltSinkRnodes().removeIf((rnode) -> rnode.getOccupancy() > 0); + } + // Wait for all outstanding RouteNodeGraph.asyncPreserve() calls to complete routingGraph.awaitPreserve(); } @@ -549,10 +564,53 @@ protected NetWrapper createNetWrapperAndConnections(Net net) { RouteNode sinkRnode = getOrCreateRouteNode(sinkINTNode, RouteNodeType.PINFEED_I); assert(sinkRnode.getType() == RouteNodeType.PINFEED_I); connection.setSinkRnode(sinkRnode); - // Assuming that each connection only has a single sink target, increment - // its usage here immediately - sinkRnode.incrementUser(netWrapper); - sinkRnode.updatePresentCongestionCost(presentCongestionFactor); + + // Where appropriate, allow all 6 LUT pins to be swapped to begin with + char lutLetter = sink.getName().charAt(0); + int numberOfSwappablePins = (lutPinSwapping && sink.isLUTInputPin()) + ? LUTTools.MAX_LUT_SIZE : 0; + if (numberOfSwappablePins > 0) { + for (Cell cell : DesignTools.getConnectedCells(sink)) { + BEL bel = cell.getBEL(); + assert(bel.isLUT()); + String belName = bel.getName(); + if (belName.charAt(0) != lutLetter) { + assert(cell.getType().startsWith("RAM")); + // This pin connects to other LUTs! (e.g. SLICEM.H[1-6] also serves + // as the WA for A-G LUTs used as distributed RAM) -- do not allow any swapping + // TODO: Relax this when https://github.com/Xilinx/RapidWright/issues/901 is fixed + numberOfSwappablePins = 0; + break; + } + if (belName.charAt(1) == '5') { + // Since a 5LUT cell exists, only allow bottom 5 pins to be swapped + numberOfSwappablePins = 5; + } + } + } + + Site site = sink.getSite(); + for (int i = 1; i <= numberOfSwappablePins; i++) { + Node node = site.getConnectedNode(lutLetter + Integer.toString(i)); + assert(node.getTile().getTileTypeEnum() == TileTypeEnum.INT); + if (node.equals(sinkINTNode)) { + continue; + } + if (routingGraph.isPreserved(node)) { + continue; + } + RouteNode altSinkRnode = getOrCreateRouteNode(node, RouteNodeType.PINFEED_I); + assert(altSinkRnode.getType() == RouteNodeType.PINFEED_I); + connection.addAltSinkRnode(altSinkRnode); + } + + if (connection.getAltSinkRnodes().isEmpty()) { + // Since this connection only has a single sink target, increment + // its usage here immediately + sinkRnode.incrementUser(netWrapper); + sinkRnode.updatePresentCongestionCost(presentCongestionFactor); + } + if (sourceINTRnode == null && altSourceINTRnode == null) { if (sourceINTNode != null) { sourceINTRnode = getOrCreateRouteNode(sourceINTNode, RouteNodeType.PINFEED_O); @@ -845,7 +903,32 @@ private List getCongestedConnections() { */ protected void postRouteProcess() { if (routeIteration <= config.getMaxIterations()) { + // perform LUT pin mapping updates + if (lutPinSwapping && + !Boolean.getBoolean("rapidwright.rwroute.lutPinSwapping.deferIntraSiteRoutingUpdates")) { + Map pinSwaps = new HashMap<>(); + for (Connection connection: indirectConnections) { + SitePinInst oldSinkSpi = connection.getSink(); + if (!oldSinkSpi.isLUTInputPin() || !oldSinkSpi.isRouted()) { + continue; + } + + List rnodes = connection.getRnodes(); + RouteNode newSinkRnode = rnodes.get(0); + if (newSinkRnode == connection.getSinkRnode()) { + continue; + } + connection.setSinkRnode(newSinkRnode); + + SitePin newSitePin = newSinkRnode.getNode().getSitePin(); + String existing = pinSwaps.put(oldSinkSpi, newSitePin.getPinName()); + assert(existing == null); + } + LUTTools.swapMultipleLutPins(pinSwaps); + } + assignNodesToConnections(); + // fix routes with cycles and / or multi-driver nodes Set routes = fixRoutes(); if (config.isTimingDriven()) updateTimingAfterFixingRoutes(routes); @@ -926,10 +1009,12 @@ protected boolean shouldRoute(Connection connection) { } } - // Check that sink node is exclusive to this connection, is used but never overused - RouteNode sinkRnode = connection.getSinkRnode(); - assert(sinkRnode.countConnectionsOfUser(connection.getNetWrapper()) > 0); - assert(!sinkRnode.isOverUsed()); + if (connection.getAltSinkRnodes().isEmpty()) { + // Check that this connection's exclusive sink node is used but never overused + RouteNode sinkRnode = connection.getSinkRnode(); + assert (sinkRnode.countConnectionsOfUser(connection.getNetWrapper()) > 0); + assert(!sinkRnode.isOverUsed()); + } return !connection.getSink().isRouted() || connection.isCongested(); } @@ -984,14 +1069,25 @@ private void updateTimingAfterFixingRoutes(Set netsWithIllegalRoutes protected void assignNodesToConnections() { for (Connection connection : indirectConnections) { List nodes = new ArrayList<>(); - List switchBoxToSink = RouterHelper.findPathBetweenNodes(connection.getSinkRnode().getNode(), connection.getSink().getConnectedNode()); - if (switchBoxToSink.size() >= 2) { - for (int i = 0; i < switchBoxToSink.size() -1; i++) { - nodes.add(switchBoxToSink.get(i)); + + RouteNode sinkRnode = connection.getSinkRnode(); + List rnodes = connection.getRnodes(); + if (sinkRnode == rnodes.get(0)) { + List switchBoxToSink = RouterHelper.findPathBetweenNodes(sinkRnode.getNode(), connection.getSink().getConnectedNode()); + if (switchBoxToSink.size() >= 2) { + for (int i = 0; i < switchBoxToSink.size() - 1; i++) { + nodes.add(switchBoxToSink.get(i)); + } } + } else { + // Routing must go to an alternate sink + assert(!connection.getAltSinkRnodes().isEmpty()); + + // Assume that it doesn't need unprojecting back to the sink pin + // since the sink node is a site pin + assert(rnodes.get(0).getNode().getSitePin() != null); } - List rnodes = connection.getRnodes(); for (RouteNode rnode : rnodes) { nodes.add(rnode.getNode()); } @@ -1228,11 +1324,11 @@ private boolean shouldMergePath(Connection connection) { * Rips up a connection. * @param connection The connection to be ripped up. */ - private void ripUp(Connection connection) { + protected void ripUp(Connection connection) { List rnodes = connection.getRnodes(); if (rnodes.isEmpty()) { assert(!connection.getSink().isRouted()); - if (connection.getAltSinkRnode() == null) { + if (connection.getAltSinkRnodes().isEmpty()) { // If there is no alternate sink, decrement this one-and-only sink node RouteNode sinkRnode = connection.getSinkRnode(); rnodes = Collections.singletonList(sinkRnode); @@ -1341,8 +1437,8 @@ protected void routeConnection(Connection connection) { assert(connection.getRnodes().isEmpty()); assert(!connection.getSink().isRouted()); - // Undo what ripUp() did for the one-and-only sink node - if (connection.getAltSinkRnode() == null) { + if (connection.getAltSinkRnodes().isEmpty()) { + // Undo what ripUp() did for this connection which has a single exclusive sink RouteNode sinkRnode = connection.getSinkRnode(); sinkRnode.incrementUser(connection.getNetWrapper()); sinkRnode.updatePresentCongestionCost(presentCongestionFactor); @@ -1423,8 +1519,8 @@ protected void finishRouteConnection(Connection connection, RouteNode rnode) { */ private boolean saveRouting(Connection connection, RouteNode rnode) { RouteNode sinkRnode = connection.getSinkRnode(); - RouteNode altSinkRnode = connection.getAltSinkRnode(); - if (rnode != sinkRnode && rnode != altSinkRnode) { + List altSinkRnodes = connection.getAltSinkRnodes(); + if (rnode != sinkRnode && !altSinkRnodes.contains(rnode)) { List prevRouting = connection.getRnodes(); // Check that this is the sink path marked by prepareRouteConnection() if (!connection.getSink().isRouted() || prevRouting.isEmpty() || !rnode.isTarget()) { @@ -1510,12 +1606,19 @@ private void exploreAndExpand(RouteNode rnode, Connection connection, float shar } if (childRNode.isTarget()) { - // Despite the limitation above, on encountering a target only terminate immediately - // by clearing the queue if childRnode is the one and only sink on this connection, - // otherwise terminate if this target will not be overused since we may find that - // the alternate sink is less congested - if ((childRNode == connection.getSinkRnode() && connection.getAltSinkRnode() == null) || - !childRNode.willOverUse(connection.getNetWrapper())) { + boolean earlyTermination = false; + if (childRNode == connection.getSinkRnode() && connection.getAltSinkRnodes().isEmpty()) { + // This sink must be exclusively reserved for this connection already + assert(childRNode.getOccupancy() == 0 || + childRNode.getNode().getIntentCode() == IntentCode.NODE_PINBOUNCE); + earlyTermination = true; + } else { + // Target is not an exclusive sink, only early terminate if this net will not + // (further) overuse this node + earlyTermination = !childRNode.willOverUse(connection.getNetWrapper()); + } + + if (earlyTermination) { assert(!childRNode.isVisited(connectionsRouted)); nodesPushed += queue.size(); queue.clear(); @@ -1537,7 +1640,7 @@ private void exploreAndExpand(RouteNode rnode, Connection connection, float shar break; case PINBOUNCE: // A PINBOUNCE can only be a target if this connection has an alternate sink - assert(!childRNode.isTarget() || connection.getAltSinkRnode() != null); + assert(!childRNode.isTarget() || connection.getAltSinkRnodes().isEmpty()); if (!isAccessiblePinbounce(childRNode, connection)) { continue; } @@ -1595,7 +1698,8 @@ protected boolean isAccessiblePinbounce(RouteNode child, Connection connection) } protected boolean isAccessiblePinfeedI(RouteNode child, Connection connection) { - return isAccessiblePinfeedI(child, connection, true); + // When LUT pin swapping is enabled, PINFEED_I are not exclusive anymore + return isAccessiblePinfeedI(child, connection, !lutPinSwapping); } protected boolean isAccessiblePinfeedI(RouteNode child, Connection connection, boolean assertOnOveruse) { @@ -1769,7 +1873,7 @@ protected void prepareRouteConnection(Connection connectionToRoute, float shareW assert(queue.isEmpty()); // Sets the sink rnode(s) of the connection as the target(s) - connectionToRoute.setTarget(true); + connectionToRoute.setAllTargets(true); // Adds the source rnode to the queue RouteNode sourceRnode = connectionToRoute.getSourceRnode(); diff --git a/src/com/xilinx/rapidwright/rwroute/RWRouteConfig.java b/src/com/xilinx/rapidwright/rwroute/RWRouteConfig.java index 59e1059b1..8ddce021a 100644 --- a/src/com/xilinx/rapidwright/rwroute/RWRouteConfig.java +++ b/src/com/xilinx/rapidwright/rwroute/RWRouteConfig.java @@ -88,6 +88,8 @@ public class RWRouteConfig { private boolean exportOutOfContext; /** Maximum presentCongestionFactor value that should prevent accuracy loss **/ private float maxPresentCongestionFactor; + /* true to enable LUT pin swapping */ + private boolean lutPinSwapping; /** Constructs a Configuration Object */ public RWRouteConfig(String[] arguments) { @@ -215,6 +217,9 @@ private void parseArguments(String[] arguments) { case "--outOfContext": setExportDesignOutOfContext(true); break; + case "--lutPinSwapping": + setLutPinSwapping(true); + break; default: throw new IllegalArgumentException("ERROR: RWRoute argument '" + arg + "' not recognized."); } @@ -705,6 +710,16 @@ public boolean getExportOutOfContext() { return exportOutOfContext; } + /** + * Gets the flag indicating if LUT pin swapping is enabled. + * Default: false. + * + * @return True if the flag is set, false otherwise. + */ + public boolean getLutPinSwapping() { + return lutPinSwapping; + } + /** * Sets critical path delay pessimism factor b. It should be greater than 0. * Default: 100. Can be modified by using "--pessimismB" option, e.g. @@ -798,6 +813,16 @@ public void setExportDesignOutOfContext(boolean exportOutOfContext) { this.exportOutOfContext = exportOutOfContext; } + /** + * Sets a flag indicating LUT pins can be swapped. + * Default: false. + * + * @param lutPinSwapping true to enable LUT pin swapping. + */ + public void setLutPinSwapping(boolean lutPinSwapping) { + this.lutPinSwapping = lutPinSwapping; + } + /** * Sets verbose. * If true, there will be more info in the routing log file regarding design netlist, routing statistics, and timing report. diff --git a/test/src/com/xilinx/rapidwright/design/TestDesignTools.java b/test/src/com/xilinx/rapidwright/design/TestDesignTools.java index 13c46d41a..b8d8ac48c 100644 --- a/test/src/com/xilinx/rapidwright/design/TestDesignTools.java +++ b/test/src/com/xilinx/rapidwright/design/TestDesignTools.java @@ -1324,6 +1324,20 @@ public void testGetConnectedCells() { "processor/data_path_loop[6].arith_logical_flop(BEL: GFF), processor/data_path_loop[7].arith_logical_flop(BEL: HFF)]", DesignTools.getConnectedCells(spi).stream().map(Cell::toString).sorted().collect(Collectors.toList()).toString()); } + + si = design.getSiteInstFromSiteName("SLICE_X15Y239"); + // Only D5LUT is present + { + // Connected to VCC + SitePinInst spi = si.getSitePinInst("D6"); + Assertions.assertEquals("[]", + DesignTools.getConnectedCells(spi).stream().map(Cell::toString).sorted().collect(Collectors.toList()).toString()); + } + { + SitePinInst spi = si.getSitePinInst("D5"); + Assertions.assertEquals("[processor/output_port_z[7]_i_1(BEL: D5LUT)]", + DesignTools.getConnectedCells(spi).stream().map(Cell::toString).sorted().collect(Collectors.toList()).toString()); + } } @Test diff --git a/test/src/com/xilinx/rapidwright/design/tools/TestLUTTools.java b/test/src/com/xilinx/rapidwright/design/tools/TestLUTTools.java index a616c96c0..84772da40 100644 --- a/test/src/com/xilinx/rapidwright/design/tools/TestLUTTools.java +++ b/test/src/com/xilinx/rapidwright/design/tools/TestLUTTools.java @@ -34,6 +34,11 @@ import com.xilinx.rapidwright.design.Unisim; import com.xilinx.rapidwright.device.Node; import com.xilinx.rapidwright.device.PIP; +import com.xilinx.rapidwright.rwroute.RWRoute; +import com.xilinx.rapidwright.rwroute.TestRWRoute; +import com.xilinx.rapidwright.support.LargeTest; +import com.xilinx.rapidwright.support.RapidWrightDCP; +import com.xilinx.rapidwright.util.Utils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -44,6 +49,8 @@ import com.xilinx.rapidwright.device.Series; import com.xilinx.rapidwright.device.Site; import com.xilinx.rapidwright.device.SiteTypeEnum; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; public class TestLUTTools { @@ -122,4 +129,25 @@ public void testSwapLutPinsFromPIPs() { Assertions.assertEquals("I1", cell6.getLogicalPinMapping("A2")); Assertions.assertEquals("I0", cell5.getLogicalPinMapping("A3")); } + + @ParameterizedTest + @CsvSource({ + "bnn.dcp", + "optical-flow.dcp", + }) + @LargeTest(max_memory_gb = 8) + public void testUpdateLutPinSwapsFromPIPsWithRWRoute(String path) { + Design design = RapidWrightDCP.loadDCP(path); + try { + System.setProperty("rapidwright.rwroute.lutPinSwapping.deferIntraSiteRoutingUpdates", "true"); + RWRoute.routeDesignWithUserDefinedArguments(design, new String[]{"--nonTimingDriven", "--lutPinSwapping"}); + int numPinsSwapped = LUTTools.swapLutPinsFromPIPs(design); + Assertions.assertTrue(numPinsSwapped > 0); + TestRWRoute.assertAllSourcesRoutedFlagSet(design); + TestRWRoute.assertAllPinsRouted(design); + TestRWRoute.assertVivadoFullyRouted(design); + } finally { + System.setProperty("rapidwright.rwroute.lutPinSwapping.deferIntraSiteRoutingUpdates", "false"); + } + } } diff --git a/test/src/com/xilinx/rapidwright/rwroute/TestRWRoute.java b/test/src/com/xilinx/rapidwright/rwroute/TestRWRoute.java index e7f99aaa0..77c78de47 100644 --- a/test/src/com/xilinx/rapidwright/rwroute/TestRWRoute.java +++ b/test/src/com/xilinx/rapidwright/rwroute/TestRWRoute.java @@ -38,6 +38,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; import com.xilinx.rapidwright.design.Cell; import com.xilinx.rapidwright.design.Design; @@ -46,6 +47,7 @@ import com.xilinx.rapidwright.design.SiteInst; import com.xilinx.rapidwright.design.SitePinInst; import com.xilinx.rapidwright.design.Unisim; +import com.xilinx.rapidwright.design.tools.TestLUTTools; import com.xilinx.rapidwright.device.Device; import com.xilinx.rapidwright.device.Node; import com.xilinx.rapidwright.device.PIP; @@ -92,7 +94,7 @@ public static void assertVivadoFullyRouted(Design design) { Assertions.assertTrue(rrs.isFullyRouted()); } - private static void assertAllPinsRouted(Design design) { + public static void assertAllPinsRouted(Design design) { for (Net net : design.getNets()) { if (net.getSource() == null && !net.isStaticNet()) { // Source-less nets may exist in out-of-context design @@ -102,7 +104,7 @@ private static void assertAllPinsRouted(Design design) { } } - private static void assertAllSourcesRoutedFlagSet(Design design) { + public static void assertAllSourcesRoutedFlagSet(Design design) { for (Net net : design.getNets()) { if (net.getSource() == null) { // Source-less nets may exist in out-of-context design @@ -142,7 +144,22 @@ public void testNonTimingDrivenFullRouting() { assertVivadoFullyRouted(design); } - + /** + * Tests the non-timing driven full routing with LUT pin swapping enabled. + */ + @ParameterizedTest + @ValueSource(strings = { + "bnn.dcp", + "optical-flow.dcp" + }) + @LargeTest(max_memory_gb = 8) + public void testNonTimingDrivenFullRoutingWithLutPinSwapping(String path) { + Design design = RapidWrightDCP.loadDCP(path); + RWRoute.routeDesignWithUserDefinedArguments(design, new String[] {"--nonTimingDriven", "--lutPinSwapping"}); + assertAllSourcesRoutedFlagSet(design); + assertAllPinsRouted(design); + assertVivadoFullyRouted(design); + } /** * Tests the timing driven full routing, i.e., RWRoute running in timing-driven mode.