diff --git a/src/com/xilinx/rapidwright/router/VersalClockRouting.java b/src/com/xilinx/rapidwright/router/VersalClockRouting.java index 7cebf8d25..80b668365 100644 --- a/src/com/xilinx/rapidwright/router/VersalClockRouting.java +++ b/src/com/xilinx/rapidwright/router/VersalClockRouting.java @@ -22,8 +22,10 @@ package com.xilinx.rapidwright.router; +import java.io.InputStream; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; @@ -51,6 +53,8 @@ import com.xilinx.rapidwright.rwroute.NodeStatus; import com.xilinx.rapidwright.rwroute.RouterHelper; import com.xilinx.rapidwright.rwroute.RouterHelper.NodeWithPrev; +import com.xilinx.rapidwright.util.FileTools; +import com.xilinx.rapidwright.util.Pair; import com.xilinx.rapidwright.util.Utils; /** @@ -65,6 +69,8 @@ public class VersalClockRouting { private static final EnumSet vRouteTypes; private static final EnumSet allRouteTypes; + private static Map> vdistrTrees; + static { hRouteTypes = EnumSet.of( IntentCode.NODE_GLOBAL_HROUTE, @@ -262,11 +268,10 @@ public static Map routeVrouteToVerticalDistributionLines(Net IntentCode.NODE_GLOBAL_VDISTR_SHARED, IntentCode.NODE_GLOBAL_GCLK); - Set startingPoints = new HashSet<>(); // The VROUTE node is the precursor to the clock root, technically the first // VDISTR node is the center point. If we have more than one VROUTE->VDISTR // transition we end up with multiple clock roots - startingPoints.add(new NodeWithPrevAndCost(vroute)); + NodeWithPrevAndCost clockRootNode = new NodeWithPrevAndCost(vroute); // Identify top and bottom clock region spine targets int minY = Integer.MAX_VALUE; @@ -282,46 +287,55 @@ public static Map routeVrouteToVerticalDistributionLines(Net verticalSpineCRs.add(device.getClockRegion(i, x)); } - nextClockRegion: for (ClockRegion cr : verticalSpineCRs) { + VersalClockTree clkTree = getVersalClockTree(device, minY, maxY); + assert (clkTree != null); + if (clkTree == null) { + System.err.println("ERROR: No clock tree found for " + device + " Y" + minY + "-Y" + maxY + + " while routing clock " + clk + ", skew will be suboptimal."); + } + + for (ClockRegion cr : verticalSpineCRs) { q.clear(); visited.clear(); - q.addAll(startingPoints); - Tile crApproxCenterTile = cr.getApproximateCenter(); - while (!q.isEmpty()) { - NodeWithPrevAndCost curr = q.poll(); - if (getNodeStatus.apply(curr) != NodeStatus.AVAILABLE) { - continue; - } - 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())) { + q.add(clockRootNode); + + List> distrPath = getVDistrPath(clkTree, cr); + nextDistrLevel: for (Pair target : distrPath) { + IntentCode targetIC = target.getFirst(); + ClockRegion targetCR = target.getSecond(); + Tile crApproxCenterTile = targetCR.getApproximateCenter(); + + while (!q.isEmpty()) { + NodeWithPrevAndCost curr = q.poll(); + if (getNodeStatus.apply(curr) != NodeStatus.AVAILABLE) { continue; } - if (!visited.add(downhill)) { - continue; + IntentCode currIC = curr.getIntentCode(); + ClockRegion currCR = curr.getTile().getClockRegion(); + if (currCR != null && targetCR == currCR && currIC == targetIC) { + q.clear(); + visited.clear(); + q.add(curr); + allPIPs.addAll(RouterHelper.getPIPsFromNodes(curr.getPrevPath())); + if (targetIC == IntentCode.NODE_GLOBAL_VDISTR) { + crToVdist.put(cr, curr); + } + continue nextDistrLevel; + } + + for (Node downhill : curr.getAllDownhillNodes()) { + if (!allowedIntentCodes.contains(downhill.getIntentCode())) { + continue; + } + if (!visited.add(downhill)) { + continue; + } + int cost = downhill.getTile().getManhattanDistance(crApproxCenterTile) + (curr.depth * 100); + q.add(new NodeWithPrevAndCost(downhill, curr, cost)); } - int cost = downhill.getTile().getManhattanDistance(crApproxCenterTile) + curr.depth; - q.add(new NodeWithPrevAndCost(downhill, curr, cost)); } + throw new RuntimeException("ERROR: Couldn't route to distribution line in clock region " + cr); } - throw new RuntimeException("ERROR: Couldn't route to distribution line in clock region " + cr); } clk.getPIPs().addAll(allPIPs); @@ -343,6 +357,12 @@ public static Map routeVrouteToVerticalDistributionLines(Net return crToVdist; } + private static List> getVDistrPath(VersalClockTree clkTree, + ClockRegion target) { + return clkTree == null ? Arrays.asList(new Pair<>(IntentCode.NODE_GLOBAL_VDISTR, target)) + : clkTree.getClockRegionVDistrPath(target); + } + /** * 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. @@ -649,4 +669,45 @@ public static void routeNonLCBPins(Net clk, List sinks, } clk.getPIPs().addAll(allPIPs); } + + @SuppressWarnings("unchecked") + public static Map> readVersalVDistrTrees() { + InputStream is = FileTools.getRapidWrightResourceInputStream(FileTools.VERSAL_VDISTR_TREES_FILE_NAME); + return (Map>) FileTools.readObjectFromKryoFile(is); + } + + public static void writeVersalVDistrTreesFile() { + String fileName = FileTools.getRapidWrightResourceFileName(FileTools.VERSAL_VDISTR_TREES_FILE_NAME); + if (vdistrTrees == null) { + throw new RuntimeException("ERROR: Cannot write file '" + fileName + "', source map is null."); + } + FileTools.writeObjectToKryoFile(fileName, vdistrTrees); + } + + public static VersalClockTree getVersalClockTree(Device device, int minY, int maxY) { + Map map = getDeviceVDistrTrees(device.getName()); + return map == null ? null : map.get(VersalClockTree.getMinMaxYRangeKey(minY, maxY)); + } + + private static Map getDeviceVDistrTrees(String deviceName) { + if (vdistrTrees == null) { + vdistrTrees = readVersalVDistrTrees(); + } + return vdistrTrees.get(deviceName); + } + + /** + * Given a range of occupied clock region Y coordinates, get the preferred clock + * root Y coordinate. + * + * @param device The current device to target. + * @param minY The smallest Y coordinate of the clock region range. + * @param maxY The largest Y coordinate of the clock region range. + * @return The preferred clock region Y coordinate for the given range or null + * if none could be found. + */ + public static Integer getPreferredClockRootYCoord(Device device, int minY, int maxY) { + VersalClockTree clkTree = getVersalClockTree(device, minY, maxY); + return clkTree == null ? null : clkTree.getPreferredClockRootYCoord(); + } } diff --git a/src/com/xilinx/rapidwright/router/VersalClockTree.java b/src/com/xilinx/rapidwright/router/VersalClockTree.java new file mode 100644 index 000000000..612200be4 --- /dev/null +++ b/src/com/xilinx/rapidwright/router/VersalClockTree.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Chris Lavin, 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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.xilinx.rapidwright.device.ClockRegion; +import com.xilinx.rapidwright.device.IntentCode; +import com.xilinx.rapidwright.util.Pair; + +/** + * For a given clock region Y coordinate domain range (e.g. Y1-Y5), provides a + * set of clock root options with corresponding vertical distribution paths for + * each target clock region. + */ +public class VersalClockTree { + + private int minMaxYKey; + + private int preferredClockRootY; + + /** + * The key to this map is the clock root y coordinate. The value is a list of + * vertical distribution paths for each target clock region. + */ + private Map clockRootPathSets; + + /** + * Only used for serialization purposes + */ + public VersalClockTree() { + + } + + public VersalClockTree(int minY, int maxY) { + this.minMaxYKey = getMinMaxYRangeKey(minY, maxY); + } + + public int getMinY() { + return minMaxYKey >>> 16; + } + + public int getMaxY() { + return minMaxYKey & 0xffff; + } + + public int getMinMaxYKey() { + return minMaxYKey; + } + + public int getPreferredClockRootYCoord() { + return preferredClockRootY; + } + + public void setPreferredClockRootYCoord(int clockRootYCoord) { + this.preferredClockRootY = clockRootYCoord; + } + + public Map getClockRootPathSets() { + return clockRootPathSets; + } + + public void setClockRootPathSets(Map clockRootPathSets) { + this.clockRootPathSets = clockRootPathSets; + } + + public Set getClockRootOptions() { + return clockRootPathSets.keySet(); + } + + /** + * Gets the vertical distribution path of the target clock region for the + * preferred clock root. + * + * @param target The destination clock region that needs to be routed via + * vertical distribution lines. + * @return A sequence of vertical distribution constraints (target IntentCode + * and ClockRegion). + */ + public List> getClockRegionVDistrPath(ClockRegion target) { + return getClockRegionVDistrPath(target, preferredClockRootY); + } + + /** + * Gets the vertical distribution path of the target clock region for the + * provided clock root. + * + * @param target The destination clock region that needs to be routed + * via vertical distribution lines. + * @param clockRootYCoord The desired clock root Y coordinate path set to use. + * @return A sequence of vertical distribution constraints (target IntentCode + * and ClockRegion). + */ + public List> getClockRegionVDistrPath(ClockRegion target, + int clockRootYCoord) { + int[][] pathData = clockRootPathSets.get(clockRootYCoord); + + if (pathData == null) { + System.err.println( + "Missing VDISTR tree for " + target.getDevice() + " targeting CR " + target); + Pair simple = new Pair<>(IntentCode.NODE_GLOBAL_VDISTR, target); + return Collections.singletonList(simple); + } + + int[] crPathData = pathData[target.getInstanceY()]; + List> vdistrPath = new ArrayList<>(crPathData.length); + for (int value : crPathData) { + IntentCode vdistrCode = IntentCode.values()[value >>> 16]; + int crY = value & 0xffff; + ClockRegion cr = target.getDevice().getClockRegion(crY, target.getInstanceX()); + vdistrPath.add(new Pair<>(vdistrCode, cr)); + } + return vdistrPath; + } + + public static int getMinMaxYRangeKey(int minY, int maxY) { + assert (minY >= 0 && maxY >= 0 && maxY >= minY); + return (minY << 16) | maxY; + } +} diff --git a/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java b/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java index ac967dee0..0ad449526 100644 --- a/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java +++ b/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java @@ -26,6 +26,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; @@ -303,7 +304,7 @@ private static void symmetricClockRoutingVersal(Net clk, Device device, // we may fail to do so. Thus, we need to force the Y-coordinate of centroid to be 1. Node clkRoutingLine = VersalClockRouting.routeBUFGToNearestRoutingTrack(clk, getNodeStatus);// first HROUTE Pair result = findCentroid(clk, clkRoutingLine, centroid, true, - getNodeStatus, unavailableTracks); + getNodeStatus, unavailableTracks, usedCRsAndNonLCBPinsTuple.getFirst()); centroidHRouteNode = result.getFirst(); centroid = result.getSecond(); } else if (sourceTypeEnum == SiteTypeEnum.BUFG_PS) { @@ -321,7 +322,8 @@ private static void symmetricClockRoutingVersal(Net clk, Device device, boolean noVrouteNeeded = centroidHRouteNode.getTile().getClockRegion().getRow() == centroid.getRow(); Pair centroidResult = findCentroid(clk, centroidHRouteNode, centroid, - noVrouteNeeded, getNodeStatus, unavailableTracks); + noVrouteNeeded, getNodeStatus, unavailableTracks, + usedCRsAndNonLCBPinsTuple.getFirst()); Node vroute = centroidResult.getFirst(); centroid = centroidResult.getSecond(); @@ -373,28 +375,46 @@ private static void symmetricClockRoutingVersal(Net clk, Device device, * print of this clock net. * @return */ - private static Pair findCentroid(Net clk, Node start, - ClockRegion origCentroid, boolean noVrouteNeeded, - Function getNodeStatus, Set unavailableTracks) { + private static Pair findCentroid(Net clk, Node start, ClockRegion origCentroid, + boolean noVrouteNeeded, Function getNodeStatus, Set unavailableTracks, + Set clockRegions) { Node vroute = null; int currIdx = 0; - List rowOffsets = getOtherRowCentroidCandidates(origCentroid); - ClockRegion neighbor = null; + + int minY = Integer.MAX_VALUE; + int maxY = 0; + for (ClockRegion cr : clockRegions) { + minY = Math.min(minY, cr.getInstanceY()); + maxY = Math.max(maxY, cr.getInstanceY()); + } + Device device = origCentroid.getDevice(); + // Clock roots on Versal appear to only be possible on odd-numbered columns + int clkRootXCoord = origCentroid.getColumn() % 2 == 0 ? origCentroid.getInstanceX() + 1 + : origCentroid.getInstanceX(); + Integer preferredYCoord = VersalClockRouting.getPreferredClockRootYCoord(device, minY, maxY); + int clkRootYCoord = preferredYCoord == null ? origCentroid.getInstanceY() : preferredYCoord; + + // If the current column doesn't work, try neighboring ones on both sides + List colOffsets = Arrays.asList(0, -2, 2, -4, 4, -6, 6); + + ClockRegion proposedClkRoot = null; + do { - // Start with estimate centroid - neighbor = origCentroid.getNeighborClockRegion(0, rowOffsets.get(currIdx)); - if (neighbor.getApproximateCenter() != null) { - vroute = VersalClockRouting.routeToCentroid(clk, start, neighbor, noVrouteNeeded, + proposedClkRoot = device.getClockRegion(clkRootYCoord, + clkRootXCoord + colOffsets.get(currIdx)); + if (proposedClkRoot != null && proposedClkRoot.getApproximateCenter() != null) { + vroute = VersalClockRouting.routeToCentroid(clk, start, proposedClkRoot, + noVrouteNeeded, getNodeStatus, unavailableTracks); } // If we weren't successful, loop around and try neighbors currIdx++; - } while (vroute == null && currIdx < rowOffsets.size()); + } while (vroute == null && currIdx < colOffsets.size()); if (vroute == null) { throw new RuntimeException("ERROR: Unable to find a centroid CR for clock " + clk); } - assert (neighbor != null); - return new Pair(vroute, neighbor); + assert (proposedClkRoot != null); + return new Pair(vroute, proposedClkRoot); } /** @@ -547,6 +567,7 @@ private static ClockRegion findCentroid(Net clk, Device device) { centroid = centroid.getNeighborClockRegion(-1, 0); } } + return centroid; } diff --git a/src/com/xilinx/rapidwright/util/DataVersions.java b/src/com/xilinx/rapidwright/util/DataVersions.java index 92c047ea1..8bd58ba3d 100644 --- a/src/com/xilinx/rapidwright/util/DataVersions.java +++ b/src/com/xilinx/rapidwright/util/DataVersions.java @@ -394,5 +394,6 @@ public class DataVersions { dataVersionMap.put("data/partdump.csv", new Pair<>("partdump-csv", "4cb26d6ffe32c6a1e2afad64daf2fbfc")); dataVersionMap.put("data/parts.db", new Pair<>("parts-db", "36b61be8570aaea97756590a430b4618")); dataVersionMap.put("data/unisim_data.dat", new Pair<>("unisim-data-dat", "b22797c846512a5a18aeb95c012f8948")); + dataVersionMap.put("data/versal_vdistr_trees.dat", new Pair<>("versal-vdistr-trees-dat", "d5b2df52479edd7917eb7c3e17fa2f18")); } } diff --git a/src/com/xilinx/rapidwright/util/FileTools.java b/src/com/xilinx/rapidwright/util/FileTools.java index 378881b31..fdbc851d4 100644 --- a/src/com/xilinx/rapidwright/util/FileTools.java +++ b/src/com/xilinx/rapidwright/util/FileTools.java @@ -127,6 +127,8 @@ public class FileTools { public static final String PART_DB_PATH = DATA_FOLDER_NAME + File.separator + "parts.db"; /** Location of the cell pins default data file */ public static final String CELL_PIN_DEFAULTS_FILE_NAME = DATA_FOLDER_NAME + File.separator + "cell_pin_defaults.dat"; + /** Location of the Versal VDISTR Tree paths for clock routing */ + public static final String VERSAL_VDISTR_TREES_FILE_NAME = DATA_FOLDER_NAME + File.separator + "versal_vdistr_trees.dat"; /** Location of cached routethru helper files */ public static final String ROUTETHRU_FOLDER_NAME = DATA_FOLDER_NAME + File.separator + "routeThrus"; /** Common instance of the Kryo class for serialization purposes */