diff --git a/src/com/xilinx/rapidwright/router/UltraScaleClockRouting.java b/src/com/xilinx/rapidwright/router/UltraScaleClockRouting.java index ed25b11d4..a87e33961 100644 --- a/src/com/xilinx/rapidwright/router/UltraScaleClockRouting.java +++ b/src/com/xilinx/rapidwright/router/UltraScaleClockRouting.java @@ -437,7 +437,7 @@ public static void routeToLCBs(Net clk, Map> startin q.add(rn); } } - throw new RuntimeException("ERROR: Couldn't route to distribution line in clock region " + lcb); + throw new RuntimeException("ERROR: Couldn't route to leaf clock buffer " + lcb); } clk.getPIPs().addAll(allPIPs); } diff --git a/src/com/xilinx/rapidwright/router/VersalClockRouting.java b/src/com/xilinx/rapidwright/router/VersalClockRouting.java new file mode 100644 index 000000000..0d5fbcea6 --- /dev/null +++ b/src/com/xilinx/rapidwright/router/VersalClockRouting.java @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2024, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Wenhao Lin, 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.router; + +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.DesignTools; +import com.xilinx.rapidwright.design.Net; +import com.xilinx.rapidwright.design.SitePinInst; +import com.xilinx.rapidwright.device.ClockRegion; +import com.xilinx.rapidwright.device.IntentCode; +import com.xilinx.rapidwright.device.Node; +import com.xilinx.rapidwright.device.PIP; + +import com.xilinx.rapidwright.device.Tile; +import com.xilinx.rapidwright.rwroute.NodeStatus; +import com.xilinx.rapidwright.rwroute.RouterHelper; +import com.xilinx.rapidwright.rwroute.RouterHelper.NodeWithPrev; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * A collection of utility methods for routing clocks on + * the Versal architecture. + * + * Created on: Nov 1, 2024 + */ +public class VersalClockRouting { + public static class NodeWithPrevAndCost extends NodeWithPrev implements Comparable { + protected int cost; + public NodeWithPrevAndCost(Node node) { + super(node); + setCost(0); + } + public NodeWithPrevAndCost(Node node, NodeWithPrev prev, int cost) { + super(node, prev); + setCost(cost); + } + + public void setCost(int cost) { + this.cost = cost; + } + + @Override + public int compareTo(NodeWithPrevAndCost that) { + return Integer.compare(this.cost, that.cost); + } + } + + public static Node routeBUFGToNearestRoutingTrack(Net clk) { + Queue q = new ArrayDeque<>(); + q.add(new NodeWithPrev(clk.getSource().getConnectedNode())); + int watchDog = 300; + while (!q.isEmpty()) { + NodeWithPrev curr = q.poll(); + IntentCode c = curr.getIntentCode(); + if (c == IntentCode.NODE_GLOBAL_HROUTE_HSR) { + List path = curr.getPrevPath(); + clk.getPIPs().addAll(RouterHelper.getPIPsFromNodes(path)); + return curr; + } + for (Node downhill: curr.getAllDownhillNodes()) { + q.add(new NodeWithPrev(downhill, curr)); + } + if (watchDog-- == 0) { + break; + } + } + return null; + } + + /** + * Routes a clock from a routing track to a transition point where the clock. + * fans out and transitions from clock routing tracks to clock distribution. + * @param clk The current clock net to contribute routing. + * @param startingNode The intermediate start point of the clock route. + * @param clockRegion The center clock region or the clock region that is one row above or below the center. + * @param findCentroidHroute The flag to indicate the returned Node should be HROUTE in the center or VROUTE going up or down. + */ + public static Node routeToCentroid(Net clk, Node startingNode, ClockRegion clockRegion, boolean findCentroidHroute) { + Queue q = new PriorityQueue<>(); + q.add(new NodeWithPrevAndCost(startingNode)); + int watchDog = 10000; + Set visited = new HashSet<>(); + Tile crApproxCenterTile = clockRegion.getApproximateCenter(); + + // In Vivado solutions, we can always find the pattern: + // ... -> NODE_GLOBAL_GCLK -> NODE_GLOBAL_VROUTE -> NODE_GLOBAL_VDISTR_LVL2 -> ... + // and this is how we locate the VROUTE node + + while (!q.isEmpty()) { + NodeWithPrevAndCost curr = q.poll(); + boolean possibleCentroid = false; + Node parent = curr.getPrev(); + if (parent != null) { + IntentCode parentIntentCode = parent.getIntentCode(); + IntentCode currIntentCode = curr.getIntentCode(); + if (parentIntentCode == IntentCode.NODE_GLOBAL_VROUTE && + currIntentCode == IntentCode.NODE_GLOBAL_HROUTE_HSR) { + // Disallow ability to go from VROUTE back to HROUTE + continue; + } + if (currIntentCode == IntentCode.NODE_GLOBAL_GCLK && + parentIntentCode == IntentCode.NODE_GLOBAL_VROUTE && + clockRegion.equals(curr.getTile().getClockRegion()) && + clockRegion.equals(parent.getTile().getClockRegion()) && + parent.getWireName().contains("BOT")) { + possibleCentroid = true; + } + } + for (Node downhill : curr.getAllDownhillNodes()) { + IntentCode downhillIntentCode = downhill.getIntentCode(); + // Only using routing lines to get to centroid + if (!downhillIntentCode.isVersalClocking()) { + continue; + } + + if (possibleCentroid && downhillIntentCode == IntentCode.NODE_GLOBAL_VDISTR_LVL2) { + NodeWithPrev centroidHRouteNode = curr.getPrev(); + if (findCentroidHroute) { + while (centroidHRouteNode.getIntentCode() != IntentCode.NODE_GLOBAL_HROUTE_HSR) { + centroidHRouteNode = centroidHRouteNode.getPrev(); + } + } + List path = centroidHRouteNode.getPrevPath(); + clk.getPIPs().addAll(RouterHelper.getPIPsFromNodes(path)); + return centroidHRouteNode; + } + + if (!findCentroidHroute && downhillIntentCode == IntentCode.NODE_GLOBAL_HROUTE_HSR) { + continue; + } + if (!visited.add(downhill)) { + continue; + } + + int cost = downhill.getTile().getManhattanDistance(crApproxCenterTile); + q.add(new NodeWithPrevAndCost(downhill, curr, cost)); + } + if (watchDog-- == 0) { + throw new RuntimeException("ERROR: Could not route from " + startingNode + " to clock region " + clockRegion); + } + } + + return null; + } + + public static Map routeVrouteToVerticalDistributionLines(Net clk, + Node vroute, + Collection clockRegions, + Function getNodeStatus) { + Map crToVdist = new HashMap<>(); + Queue q = new PriorityQueue<>(); + Set visited = new HashSet<>(); + Set allPIPs = new HashSet<>(); + Set startingPoints = new HashSet<>(); + startingPoints.add(new NodeWithPrevAndCost(vroute)); + // Pattern: NODE_GLOBAL_VROUTE -> ... -> NODE_GLOBAL_VDISTR_LVL2 -> ... -> NODE_GLOBAL_VDISTR_LVL1 -> ... -> NODE_GLOBAL_VDISTR + Set allowedIntentCodes = EnumSet.of( + IntentCode.NODE_GLOBAL_VDISTR, + IntentCode.NODE_GLOBAL_VDISTR_LVL1, + IntentCode.NODE_GLOBAL_VDISTR_LVL2, + IntentCode.NODE_GLOBAL_GCLK + ); + nextClockRegion: for (ClockRegion cr : clockRegions) { + q.clear(); + visited.clear(); + q.addAll(startingPoints); + Tile crApproxCenterTile = cr.getApproximateCenter(); + while (!q.isEmpty()) { + NodeWithPrevAndCost curr = q.poll(); + IntentCode c = curr.getIntentCode(); + ClockRegion currCR = curr.getTile().getClockRegion(); + if (currCR != null && cr.getRow() == currCR.getRow() && c == IntentCode.NODE_GLOBAL_VDISTR) { + // Only consider base wires + if (getNodeStatus.apply(curr) == NodeStatus.INUSE) { + startingPoints.add(curr); + } else { + List path = curr.getPrevPath(); + for (Node node : path) { + startingPoints.add(new NodeWithPrevAndCost(node)); + } + allPIPs.addAll(RouterHelper.getPIPsFromNodes(path)); + } + crToVdist.put(cr, curr); + continue nextClockRegion; + } + + for (Node downhill : curr.getAllDownhillNodes()) { + if (!allowedIntentCodes.contains(downhill.getIntentCode())) { + continue; + } + if (!visited.add(downhill)) { + continue; + } + int cost = downhill.getTile().getManhattanDistance(crApproxCenterTile); + q.add(new NodeWithPrevAndCost(downhill, curr, cost)); + } + } + throw new RuntimeException("ERROR: Couldn't route to distribution line in clock region " + cr); + } + clk.getPIPs().addAll(allPIPs); + return crToVdist; + } + + /** + * For each target clock region, route from the provided vertical distribution line to a + * horizontal distribution line that has a GLOBAL_GLK child node in this clock region. + * This simulates the behavior of Vivado. + * @param clk The current clock net + * @param crMap A map of target clock regions and their respective vertical distribution lines + * @return The map of target clock regions and their respective horizontal distribution lines. + */ + public static Map routeVerticalToHorizontalDistributionLines(Net clk, + Map crMap, + Function getNodeStatus) { + Map distLines = new HashMap<>(); + Queue q = new ArrayDeque<>(); + Set allPIPs = new HashSet<>(); + Set visited = new HashSet<>(); + nextClockRegion: for (Entry e : crMap.entrySet()) { + q.clear(); + Node vertDistLine = e.getValue(); + q.add(new NodeWithPrev(vertDistLine)); + ClockRegion targetCR = e.getKey(); + visited.clear(); + visited.add(vertDistLine); + + while (!q.isEmpty()) { + NodeWithPrev curr = q.poll(); + NodeWithPrev parent = curr.getPrev(); + if (targetCR.equals(curr.getTile().getClockRegion()) && + curr.getIntentCode() == IntentCode.NODE_GLOBAL_GCLK && + parent.getIntentCode() == IntentCode.NODE_GLOBAL_HDISTR_LOCAL) { + List path = curr.getPrevPath(); + for (int i = 1; i < path.size(); i++) { + Node node = path.get(i); + NodeStatus status = getNodeStatus.apply(node); + if (status == NodeStatus.INUSE) { + break; + } + assert(status == NodeStatus.AVAILABLE); + if (i > 1) { + allPIPs.add(PIP.getArbitraryPIP(node, path.get(i-1))); + } + } + distLines.put(targetCR, parent); + continue nextClockRegion; + } + + for (Node downhill: curr.getAllDownhillNodes()) { + IntentCode intentCode = downhill.getIntentCode(); + if (intentCode != IntentCode.NODE_PINFEED && !intentCode.isVersalClocking()) { + continue; + } + if (!visited.add(downhill)) { + continue; + } + q.add(new NodeWithPrev(downhill, curr)); + } + } + throw new RuntimeException("ERROR: Couldn't route to distribution line in clock region " + targetCR); + } + clk.getPIPs().addAll(allPIPs); + return distLines; + } + + /** + * Routes from distribution lines to the leaf clock buffers (LCBs) + * @param clk The current clock net + * @param distLines A map of target clock regions and their respective horizontal distribution lines + * @param lcbTargets The target LCB nodes to route the clock + */ + public static void routeDistributionToLCBs(Net clk, Map distLines, Set lcbTargets) { + Map> startingPoints = getStartingPoints(distLines); + routeToLCBs(clk, startingPoints, lcbTargets); + } + + public static Map> getStartingPoints(Map distLines) { + Map> startingPoints = new HashMap<>(); + for (Entry e : distLines.entrySet()) { + ClockRegion cr = e.getKey(); + Node distLine = e.getValue(); + startingPoints.computeIfAbsent(cr, k -> new HashSet<>()) + .add(new NodeWithPrevAndCost(distLine)); + } + return startingPoints; + } + + public static void routeToLCBs(Net clk, Map> startingPoints, Set lcbTargets) { + Queue q = new PriorityQueue<>(); + Set allPIPs = new HashSet<>(); + Set visited = new HashSet<>(); + + nextLCB: for (Node lcb : lcbTargets) { + q.clear(); + visited.clear(); + Tile lcbTile = lcb.getTile(); + ClockRegion currCR = lcbTile.getClockRegion(); + Set starts = startingPoints.getOrDefault(currCR, Collections.emptySet()); + for (NodeWithPrev n : starts) { + assert(n.getPrev() == null); + } + q.addAll(starts); + while (!q.isEmpty()) { + NodeWithPrevAndCost curr = q.poll(); + if (lcb.equals(curr)) { + List path = curr.getPrevPath(); + allPIPs.addAll(RouterHelper.getPIPsFromNodes(path)); + + Set s = startingPoints.get(currCR); + for (Node n : path) { + s.add(new NodeWithPrevAndCost(n)); + } + + continue nextLCB; + } + for (Node downhill : curr.getAllDownhillNodes()) { + // Stay in this clock region + if (!currCR.equals(downhill.getTile().getClockRegion())) { + continue; + } + IntentCode intentCode = downhill.getIntentCode(); + if (intentCode != IntentCode.NODE_PINFEED && !intentCode.isVersalClocking()) { + continue; + } + if (downhill.getWireName().endsWith("_I_CASC_PIN") || downhill.getWireName().endsWith("_CLR_B_PIN")) { + continue; + } + if (!visited.add(downhill)) { + continue; + } + int cost = downhill.getTile().getManhattanDistance(lcbTile); + q.add(new NodeWithPrevAndCost(downhill, curr, cost)); + } + } + throw new RuntimeException("ERROR: Couldn't route to leaf clock buffer " + lcb); + } + clk.getPIPs().addAll(allPIPs); + } + + /** + * Routes from a GLOBAL_VERTICAL_ROUTE to horizontal distribution lines. + * @param clk The clock net to be routed. + * @param vroute The node to start the route. + * @param clockRegions Target clock regions. + * @param down To indicate if it is routing to the group of top clock regions. + * @return The map of target clock regions and their respective horizontal distribution lines. + */ + public static Map routeToHorizontalDistributionLines(Net clk, + Node vroute, + Collection clockRegions, + boolean down, + Function getNodeStatus) { + // First step: map each clock region to a VDISTR node. + // The clock region of this VDISTR node should be in the same column of the centroid (X) and the same row of the target clock region (Y). + Map vertDistLines = routeVrouteToVerticalDistributionLines(clk, vroute, clockRegions, getNodeStatus); + + // Second step: start from the VDISTR node and try to find a HDISTR node in the target clock region. + return routeVerticalToHorizontalDistributionLines(clk, vertDistLines, getNodeStatus); + } + + /** + * Routes a partially routed clock. + * It will examine the clock net for SitePinInsts and assumes any present are already routed. It + * then invokes {@link DesignTools#createMissingSitePinInsts(Design, Net)} to discover those not + * yet routed. + * @param design The current design + * @param clkNet The partially routed clock net to make fully routed + * @param getNodeStatus Lambda for indicating the status of a Node: available, in-use (preserved + * for same net as we're routing), or unavailable (preserved for other net). + */ + public static void incrementalClockRouter(Design design, + Net clkNet, + Function getNodeStatus) { + // TODO: + throw new RuntimeException("ERROR: Incremental clock routing not yet supported for Versal devices."); + } + + /** + * Routes a list of unrouted pins from a partially routed clock. + * @param clkNet The partially routed clock net to make fully routed + * @param clkPins A list of unrouted pins on the clock net to route + * @param getNodeStatus Lambda for indicating the status of a Node: available, in-use (preserved + * for same net as we're routing), or unavailable (preserved for other net). + */ + public static void incrementalClockRouter(Net clkNet, + List clkPins, + Function getNodeStatus) { + // TODO: + throw new RuntimeException("ERROR: Incremental clock routing not yet supported for Versal devices."); + } + + public static Map> routeLCBsToSinks(Net clk, + Function getNodeStatus) { + Map> lcbMappings = new HashMap<>(); + Set allowedIntentCodes = EnumSet.of( + IntentCode.NODE_CLE_CNODE, + IntentCode.NODE_INTF_CNODE, + IntentCode.NODE_INODE, + IntentCode.NODE_PINBOUNCE, + IntentCode.NODE_CLE_BNODE, + IntentCode.NODE_INTF_BNODE, + IntentCode.NODE_IMUX, + IntentCode.NODE_CLE_CTRL, + IntentCode.NODE_INTF_CTRL, + IntentCode.NODE_IRI, + IntentCode.NODE_PINFEED, + IntentCode.NODE_GLOBAL_LEAF + ); + Set visited = new HashSet<>(); + Queue q = new ArrayDeque<>(); + Predicate isNodeUnavailable = (node) -> getNodeStatus.apply(node) == NodeStatus.UNAVAILABLE; + RouteThruHelper routeThruHelper = new RouteThruHelper(clk.getDesign().getDevice()); + + nextPin: for (SitePinInst p: clk.getPins()) { + if (p.isOutPin()) { + continue; + } + NodeWithPrev sink = new NodeWithPrev(p.getConnectedNode()); + ClockRegion cr = p.getTile().getClockRegion(); + + q.clear(); + q.add(sink); + + while (!q.isEmpty()) { + NodeWithPrev curr = q.poll(); + for (Node uphill : curr.getAllUphillNodes()) { + if (!uphill.getTile().getClockRegion().equals(cr)) { + continue; + } + IntentCode uphillIntentCode = uphill.getIntentCode(); + if (!allowedIntentCodes.contains(uphillIntentCode)) { + continue; + } + if (!visited.add(uphill)) { + continue; + } + if (routeThruHelper.isRouteThru(uphill, curr) && curr.getIntentCode() != IntentCode.NODE_IRI) { + continue; + } + if (isNodeUnavailable.test(uphill)) { + continue; + } + NodeWithPrev node = new NodeWithPrev(uphill, curr); + if (uphillIntentCode == IntentCode.NODE_GLOBAL_LEAF) { + List path = node.getPrevPath(); + boolean srcToSinkOrder = true; + clk.getPIPs().addAll(RouterHelper.getPIPsFromNodes(path, srcToSinkOrder)); + lcbMappings.computeIfAbsent(uphill, (k) -> new ArrayList<>()).add(p); + visited.clear(); + continue nextPin; + } + q.add(node); + } + } + throw new RuntimeException("ERROR: Couldn't route pin " + sink + " to any LCB"); + } + + return lcbMappings; + } +} diff --git a/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java b/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java index b00834840..81df02ba7 100644 --- a/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java +++ b/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java @@ -24,6 +24,16 @@ package com.xilinx.rapidwright.rwroute; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.Set; +import java.util.function.Function; + import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.Net; import com.xilinx.rapidwright.design.NetType; @@ -38,6 +48,7 @@ import com.xilinx.rapidwright.device.Series; import com.xilinx.rapidwright.device.Site; import com.xilinx.rapidwright.device.SitePin; +import com.xilinx.rapidwright.device.SiteTypeEnum; import com.xilinx.rapidwright.device.Tile; import com.xilinx.rapidwright.device.TileTypeEnum; import com.xilinx.rapidwright.device.Wire; @@ -46,20 +57,12 @@ import com.xilinx.rapidwright.router.RouteNode; import com.xilinx.rapidwright.router.RouteThruHelper; import com.xilinx.rapidwright.router.UltraScaleClockRouting; +import com.xilinx.rapidwright.router.VersalClockRouting; import com.xilinx.rapidwright.util.Utils; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Comparator; import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Queue; -import java.util.Set; -import java.util.function.Function; /** * A collection of methods for routing global signals, i.e. GLOBAL_CLOCK, VCC and GND. @@ -190,9 +193,29 @@ private static Map> getListOfNodesFromRoutes(Device device, M * for same net as we're routing), or unavailable (preserved for other net). */ public static void symmetricClkRouting(Net clk, Device device, Function getNodeStatus) { + switch (device.getSeries()) { + case UltraScale: + case UltraScalePlus: + symmetricClockRoutingUltraScales(clk, device, getNodeStatus); + break; + case Versal: + symmetricClockRoutingVersal(clk, device, getNodeStatus); + break; + default: + throw new RuntimeException("ERROR: GlobalSignalRouting.symmetricClkRouting() does not support the " + device.getSeries() + " series."); + } + + Set clkPIPsWithoutDuplication = new HashSet<>(clk.getPIPs()); + clk.setPIPs(clkPIPsWithoutDuplication); + } + + private static void symmetricClockRoutingUltraScales(Net clk, Device device, Function getNodeStatus) { + // Clock routing on UltraScale/UltraScale+ devices + assert(device.getSeries() == Series.UltraScale || device.getSeries() == Series.UltraScalePlus); + List clockRegions = getClockRegionsOfNet(clk); - ClockRegion centroid = findCentroid(clk, device); + ClockRegion centroid = findCentroid(clk, device); List upClockRegions = new ArrayList<>(); List downClockRegions = new ArrayList<>(); // divides clock regions into two groups @@ -223,9 +246,59 @@ public static void symmetricClkRouting(Net clk, Device device, Function clkPIPsWithoutDuplication = new HashSet<>(clk.getPIPs()); - clk.setPIPs(clkPIPsWithoutDuplication); + private static void symmetricClockRoutingVersal(Net clk, Device device, Function getNodeStatus) { + // Clock routing on Versal devices + assert(device.getSeries() == Series.Versal); + + List clockRegions = getClockRegionsOfNet(clk); + SitePinInst source = clk.getSource(); + SiteTypeEnum sourceTypeEnum = source.getSiteTypeEnum(); + // In US/US+ clock routing, we use two VROUTE nodes to reach the clock regions above and below the centroid. + // However, we can see that Vivado only uses one VROUTE node in the centroid clock region for Versal clock routing, + // and reach the above and below clock regions by VDISTR nodes. + + ClockRegion centroid; + Node centroidHRouteNode; + + if (sourceTypeEnum == SiteTypeEnum.BUFG_FABRIC) { + // These source sites are located in the middle of the device. The path from the output pin to VROUTE matches the following pattern: + // NODE_GLOBAL_BUFG (the output node with a suffix "_O") -> + // NODE_GLOBAL_BUFG (has a suffix "_O_PIN") -> + // NODE_GLOBAL_GCLK -> + // NODE_GLOBAL_VROUTE (located in the same clock region of the source site) + + // Notice that Vivado always uses the above VROUTE node, there is no need to find a centroid clock region to route to. + centroid = source.getTile().getClockRegion(); + centroidHRouteNode = source.getConnectedNode(); + } else if (sourceTypeEnum == SiteTypeEnum.BUFGCE) { + // Assume that these source sites are located in the bottom of the device (Y=0). + // The path from the output pin to VROUTE matches the following pattern: + // NODE_GLOBAL_BUFG -> NODE_GLOBAL_BUFG -> NODE_GLOBAL_GCLK -> NODE_GLOBAL_HROUTE_HSR -> NODE_GLOBAL_VROUTE + // which is similar to US/US+ clock routing. + // Notice that we have to quickly reach a NODE_GLOBAL_HROUTE_HSR node, and if we allow the Y coordinate of centroid to be bigger than 1, + // we may fail to do so. Thus, we need to force the Y-coordinate of centroid to be 1. + assert(source.getTile().getTileYCoordinate() == 0); + // And, in X-axis, Vivado doesn't go to the real centroid of target clock regions... it just uses a nearby VROUTE. + int centroidX = source.getTile().getClockRegion().getColumn(); + // VROUTE nodes are in the clock region where X is odd. + if (centroidX % 2 == 0) centroidX -= 1; + if (centroidX <= 0) centroidX = 1; + centroid = device.getClockRegion(1, centroidX); + + Node clkRoutingLine = VersalClockRouting.routeBUFGToNearestRoutingTrack(clk);// first HROUTE + centroidHRouteNode = VersalClockRouting.routeToCentroid(clk, clkRoutingLine, centroid, true); + } else { + throw new RuntimeException("ERROR: Routing clock net with source type " + sourceTypeEnum + " not supported."); + } + + Node vroute = VersalClockRouting.routeToCentroid(clk, centroidHRouteNode, centroid, false); + + Map upDownDistLines = VersalClockRouting.routeToHorizontalDistributionLines(clk, vroute, clockRegions, false, getNodeStatus); + + Map> lcbMappings = VersalClockRouting.routeLCBsToSinks(clk, getNodeStatus); + VersalClockRouting.routeDistributionToLCBs(clk, upDownDistLines, lcbMappings.keySet()); } /** diff --git a/src/com/xilinx/rapidwright/rwroute/RouterHelper.java b/src/com/xilinx/rapidwright/rwroute/RouterHelper.java index 4eea6d50d..dcdafc4c6 100644 --- a/src/com/xilinx/rapidwright/rwroute/RouterHelper.java +++ b/src/com/xilinx/rapidwright/rwroute/RouterHelper.java @@ -30,7 +30,9 @@ import java.io.IOException; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -67,19 +69,34 @@ * A collection of supportive methods for the router. */ public class RouterHelper { - static class NodeWithPrev extends Node { + public static class NodeWithPrev extends Node { protected NodeWithPrev prev; - NodeWithPrev(Node node) { + public NodeWithPrev(Node node) { super(node); } - void setPrev(NodeWithPrev prev) { + public NodeWithPrev(Node node, NodeWithPrev prev) { + super(node); + setPrev(prev); + } + + public void setPrev(NodeWithPrev prev) { this.prev = prev; } - NodeWithPrev getPrev() { + public NodeWithPrev getPrev() { return prev; } + + public List getPrevPath() { + List path = new ArrayList<>(); + NodeWithPrev curr = this; + while (curr != null) { + path.add(curr); + curr = curr.getPrev(); + } + return path; + } } /** @@ -547,14 +564,11 @@ public static boolean routeDirectConnection(Connection directConnection) { * @return A list of nodes making up the path. */ public static List findPathBetweenNodes(Node source, Node sink) { - List path = new ArrayList<>(); if (source.equals(sink)) { - return path; // for pins without additional projected int_node + return Collections.emptyList(); // for pins without additional projected int_node } if (source.getAllDownhillNodes().contains(sink)) { - path.add(sink); - path.add(source); - return path; + return Arrays.asList(sink, source); } NodeWithPrev sourcer = new NodeWithPrev(source); sourcer.setPrev(null); @@ -566,16 +580,10 @@ public static List findPathBetweenNodes(Node source, Node sink) { !Utils.isClocking(sink.getTile().getTileTypeEnum()); int watchdog = 10000; - boolean success = false; while (!queue.isEmpty()) { NodeWithPrev curr = queue.poll(); if (curr.equals(sink)) { - while (curr != null) { - path.add(curr); - curr = curr.getPrev(); - } - success = true; - break; + return curr.getPrevPath(); } for (Node n : curr.getAllDownhillNodes()) { if (blockClocking && Utils.isClocking(n.getTile().getTileTypeEnum())) { @@ -591,11 +599,8 @@ public static List findPathBetweenNodes(Node source, Node sink) { } } - if (!success) { - System.err.println("ERROR: Failed to find a path between two nodes: " + source + ", " + sink); - path.clear(); - } - return path; + System.err.println("ERROR: Failed to find a path between two nodes: " + source + ", " + sink); + return Collections.emptyList(); } /** diff --git a/test/src/com/xilinx/rapidwright/device/TestNode.java b/test/src/com/xilinx/rapidwright/device/TestNode.java index a180fe26b..350e16937 100644 --- a/test/src/com/xilinx/rapidwright/device/TestNode.java +++ b/test/src/com/xilinx/rapidwright/device/TestNode.java @@ -259,6 +259,7 @@ public void testNodeReachabilityUltraScale(String partName, String tileName, Str "xcvp1002,CLE_W_CORE_X38Y220,NODE_CLE_OUTPUT,,false", "xcvp1002,INTF_ROCF_TR_TILE_X39Y153,NODE_INTF_CNODE,,true", "xcvp1002,INTF_ROCF_TR_TILE_X39Y153,NODE_INTF_BNODE,,true", + "xcvp1002,RCLK_CLE_CORE_X37Y239,NODE_GLOBAL_LEAF,CLK_LEAF_SITES_\\d+_O,true", }) public void testNodeReachabilityVersal(String partName, String tileName, String intentCodeName, String wireNameRegex, boolean local) { Device device = Device.getDevice(partName); @@ -292,7 +293,7 @@ public void testNodeReachabilityVersal(String partName, String tileName, String .map(s -> s.replaceFirst("(INT_NODE_IMUX_ATOM_)(3[2-9]|4[0-9]|5[0-9]|6[0-3]|9[6-9]|10[0-9]|11[0-9]|12[0-7])_INT_OUT[01]", "$1<32-63,96-127>")) .map(s -> s.replaceFirst("([BC]NODE_OUTS_[EW])\\d+", "$1")) .map(s -> s.replaceFirst("((CLE_SLICE[LM]_TOP_[01]|OUT_[NESW]NODE|(NN|EE|SS|WW)(1|2|4|6|7|10|12)(_[EW])?)_)[^ ]+", "$1")) - .map(s -> s.replaceFirst("(CLK_LEAF_SITES_)\\d+_O", "$1")) + .map(s -> s.replaceFirst("(CLK_LEAF_SITES_)\\d+_O(_PIN)?", "$1")) .map(s -> s.replaceFirst("(VCC_WIRE)\\d+", "$1")) .distinct() .sorted() diff --git a/test/src/com/xilinx/rapidwright/rwroute/TestGlobalSignalRouting.java b/test/src/com/xilinx/rapidwright/rwroute/TestGlobalSignalRouting.java index ac418bd5c..379dd822d 100644 --- a/test/src/com/xilinx/rapidwright/rwroute/TestGlobalSignalRouting.java +++ b/test/src/com/xilinx/rapidwright/rwroute/TestGlobalSignalRouting.java @@ -26,18 +26,21 @@ import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.DesignTools; import com.xilinx.rapidwright.design.Net; +import com.xilinx.rapidwright.design.NetTools; import com.xilinx.rapidwright.design.NetType; import com.xilinx.rapidwright.design.SiteInst; import com.xilinx.rapidwright.design.SitePinInst; import com.xilinx.rapidwright.design.Unisim; import com.xilinx.rapidwright.device.Device; import com.xilinx.rapidwright.device.Node; +import com.xilinx.rapidwright.device.PIP; import com.xilinx.rapidwright.device.SitePin; import com.xilinx.rapidwright.router.RouteThruHelper; import com.xilinx.rapidwright.support.RapidWrightDCP; import com.xilinx.rapidwright.util.FileTools; import com.xilinx.rapidwright.util.ReportRouteStatusResult; import com.xilinx.rapidwright.util.VivadoTools; +import com.xilinx.rapidwright.util.VivadoToolsHelper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; @@ -50,7 +53,9 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class TestGlobalSignalRouting { @ParameterizedTest @@ -317,4 +322,35 @@ public void testMuxOutPinAsStaticSourceEvenWithLutRam(boolean setFmuxCtag, boole Assertions.assertEquals("PARTIAL", status); } } + + @Test + public void testSymmetricClkRouting() { + Design design = RapidWrightDCP.loadDCP("two_clk_check_NetTools.dcp"); + design.unrouteDesign(); + // Simulate the preserve method + Set used = new HashSet<>(); + + for (String netName : Arrays.asList("clk1_IBUF_BUFG", "clk2_IBUF_BUFG", "rst1", "rst2")) { + Net net = design.getNet(netName); + Assertions.assertTrue(NetTools.isGlobalClock(net)); + GlobalSignalRouting.symmetricClkRouting(net, design.getDevice(), (n) -> used.contains(n) ? NodeStatus.UNAVAILABLE : NodeStatus.AVAILABLE); + for (PIP pip: net.getPIPs()) { + for (Node node: Arrays.asList(pip.getStartNode(), pip.getEndNode())) { + if (node != null) used.add(node); + } + } + DesignTools.updatePinsIsRouted(net); + for (SitePinInst spi : net.getPins()) { + Assertions.assertTrue(spi.isRouted()); + } + } + + if (FileTools.isVivadoOnPath()) { + ReportRouteStatusResult rrs = VivadoTools.reportRouteStatus(design); + Assertions.assertEquals(4, rrs.fullyRoutedNets); + Assertions.assertEquals(0, rrs.netsWithRoutingErrors); + } else { + System.err.println("WARNING: vivado not on PATH"); + } + } }