From 7bd8e7d95b2101a6469363fd673b626cb759a0f6 Mon Sep 17 00:00:00 2001 From: eddieh-xlnx Date: Fri, 22 Nov 2024 17:42:37 -0800 Subject: [PATCH 1/3] [CUFR] CUFR and PartialCUFR to default to --hus (#1111) Emit warning if not enabled Signed-off-by: Eddie Hung --- src/com/xilinx/rapidwright/rwroute/CUFR.java | 28 +++++++++- .../rapidwright/rwroute/PartialCUFR.java | 51 +++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/com/xilinx/rapidwright/rwroute/CUFR.java b/src/com/xilinx/rapidwright/rwroute/CUFR.java index 208776f7b..6bc362292 100644 --- a/src/com/xilinx/rapidwright/rwroute/CUFR.java +++ b/src/com/xilinx/rapidwright/rwroute/CUFR.java @@ -163,7 +163,28 @@ protected void routeIndirectConnections(Collection connections) { } /** - * Routes a {@link Design} instance. + * Routes a design in the full timing-driven routing mode using CUFR. + * @param design The {@link Design} instance to be routed. + */ + public static Design routeDesignFullTimingDriven(Design design) { + return routeDesignWithUserDefinedArguments(design, new String[] { + "--hus" + }); + } + + /** + * Routes a design in the full non-timing-driven routing mode using CUFR. + * @param design The {@link Design} instance to be routed. + */ + public static Design routeDesignFullNonTimingDriven(Design design) { + return routeDesignWithUserDefinedArguments(design, new String[] { + "--hus", + "--nonTimingDriven" + }); + } + + /** + * Routes a {@link Design} instance using CUFR. * @param design The {@link Design} instance to be routed. * @param args An array of string arguments, can be null. * If null, the design will be routed in the full timing-driven routing mode with default a {@link RWRouteConfig} instance. @@ -174,6 +195,11 @@ public static Design routeDesignWithUserDefinedArguments(Design design, String[] // Instantiates a RWRouteConfig Object and parses the arguments. // Uses the default configuration if basic usage only. RWRouteConfig config = new RWRouteConfig(args); + + if (!config.isHus()) { + System.err.println("WARNING: Hybrid Updating Strategy (HUS) is not enabled."); + } + return routeDesign(design, new CUFR(design, config)); } diff --git a/src/com/xilinx/rapidwright/rwroute/PartialCUFR.java b/src/com/xilinx/rapidwright/rwroute/PartialCUFR.java index 0d70a3965..2f435d0f9 100644 --- a/src/com/xilinx/rapidwright/rwroute/PartialCUFR.java +++ b/src/com/xilinx/rapidwright/rwroute/PartialCUFR.java @@ -151,6 +151,53 @@ protected void printRoutingStatistics() { super.printRoutingStatistics(); } + /** + * Routes a design in the partial non-timing-driven routing mode. + * @param design The {@link Design} instance to be routed. + * @param pinsToRoute Collection of {@link SitePinInst}-s to be routed. If null, route all unrouted pins in the design. + */ + public static Design routeDesignPartialNonTimingDriven(Design design, Collection pinsToRoute) { + boolean softPreserve = false; + return routeDesignPartialNonTimingDriven(design, pinsToRoute, softPreserve); + } + + /** + * Routes a design in the partial non-timing-driven routing mode using CUFR. + * @param design The {@link Design} instance to be routed. + * @param pinsToRoute Collection of {@link SitePinInst}-s to be routed. If null, route all unrouted pins in the design. + * @param softPreserve Allow routed nets to be unrouted and subsequently rerouted in order to improve routability. + */ + public static Design routeDesignPartialNonTimingDriven(Design design, Collection pinsToRoute, boolean softPreserve) { + return routeDesignWithUserDefinedArguments(design, new String[] { + "--hus", + "--fixBoundingBox", + // use U-turn nodes and no masking of nodes cross RCLK + // Pros: maximum routability + // Con: might result in delay optimism and a slight increase in runtime + "--useUTurnNodes", + "--nonTimingDriven", + "--verbose"}, + pinsToRoute, softPreserve); + } + + /** + * Routes a design in the partial timing-driven routing mode using CUFR. + * @param design The {@link Design} instance to be routed. + * @param pinsToRoute Collection of {@link SitePinInst}-s to be routed. If null, route all unrouted pins in the design. + * @param softPreserve Allow routed nets to be unrouted and subsequently rerouted in order to improve routability. + */ + public static Design routeDesignPartialTimingDriven(Design design, Collection pinsToRoute, boolean softPreserve) { + return routeDesignWithUserDefinedArguments(design, new String[] { + "--hus", + "--fixBoundingBox", + // use U-turn nodes and no masking of nodes cross RCLK + // Pros: maximum routability + // Con: might result in delay optimism and a slight increase in runtime + "--useUTurnNodes", + "--verbose"}, + pinsToRoute, softPreserve); + } + /** * Partially routes a {@link Design} instance; specifically, all nets with no routing PIPs already present. * @param design The {@link Design} instance to be routed. @@ -194,6 +241,10 @@ public static Design routeDesignWithUserDefinedArguments(Design design, System.out.println("WARNING: Masking nodes across RCLK for partial routing could result in routability problems."); } + if (!config.isHus()) { + System.err.println("WARNING: Hybrid Updating Strategy (HUS) is not enabled."); + } + return routeDesign(design, new PartialCUFR(design, config, pinsToRoute, softPreserve)); } From d75c40484328f3a492564483a100aed53fcba04f Mon Sep 17 00:00:00 2001 From: eddieh-xlnx Date: Fri, 22 Nov 2024 18:19:03 -0800 Subject: [PATCH 2/3] Various preprocessing fixes for Versal routing (#1115) * [IntentCode] Add isVersalCnode() helper method Signed-off-by: Eddie Hung Conflicts: src/com/xilinx/rapidwright/device/IntentCode.java * [GlobalSignalRouting] Allow INTF CNODEs, all CNODEs even if reserved Signed-off-by: Eddie Hung * Revert "[IntentCode] Add isVersalCnode() helper method" This reverts commit 435152a03c734d10fa41b606b0575c797cc3a922. Signed-off-by: Eddie Hung * Move Versal {B,C}NODE heuristic from determineRoutingTargets() to initializeRouting(), which is after route{GlobalClock,Static}Nets() Signed-off-by: Eddie Hung * [RouterHelper] invertPossibleGndPinsToVccPins() exception for DSP58 Signed-off-by: Eddie Hung * [DesignTools] getAllRoutedSitePinsFromPhysicalPin() handle SLICE_FF_CLK_MOD Signed-off-by: Eddie Hung * [DesignTools] getAllRoutedSitePinsFromPhysicalPin() to handle DSP_CAS_DELAY Signed-off-by: Eddie Hung * [CUFR] CUFR and PartialCUFR to default to --hus Emit warning if not enabled Signed-off-by: Eddie Hung * [RouterHelper] invertPossibleGndPinsToVccPins() to invert Versal BRAM.CLK Signed-off-by: Eddie Hung * [RouterHelper] invertPossibleGndPinsToVccPins() to use correct sitewire Signed-off-by: Eddie Hung * [DesignTools] createA1A6ToStaticNets() to handle SRL16s on LUT5+6 Signed-off-by: Eddie Hung * Fix DesignTools.createA1A6ToStaticNets() Signed-off-by: Eddie Hung * [RouteNode] Correct assertions in setBaseCost() for Versal Signed-off-by: Eddie Hung * [TestNode] Extend testNodeReachabilityVersal Signed-off-by: Eddie Hung * Update comment Signed-off-by: Eddie Hung * Update src/com/xilinx/rapidwright/design/DesignTools.java Signed-off-by: eddieh-xlnx --------- Signed-off-by: Eddie Hung Signed-off-by: eddieh-xlnx --- .../rapidwright/design/DesignTools.java | 135 ++++++++++++------ .../xilinx/rapidwright/device/IntentCode.java | 6 + .../rwroute/GlobalSignalRouting.java | 23 +-- .../xilinx/rapidwright/rwroute/RWRoute.java | 52 +++---- .../xilinx/rapidwright/rwroute/RouteNode.java | 29 +++- .../rapidwright/rwroute/RouterHelper.java | 13 +- .../xilinx/rapidwright/device/TestNode.java | 3 +- 7 files changed, 171 insertions(+), 90 deletions(-) diff --git a/src/com/xilinx/rapidwright/design/DesignTools.java b/src/com/xilinx/rapidwright/design/DesignTools.java index 54d747d44..855c948ec 100644 --- a/src/com/xilinx/rapidwright/design/DesignTools.java +++ b/src/com/xilinx/rapidwright/design/DesignTools.java @@ -2296,11 +2296,19 @@ public static List getAllRoutedSitePinsFromPhysicalPin(Cell cell, Net ne } else if (bel.isLUT() || bel.getBELType().endsWith("MUX") || // F[789]MUX // Versal + bel.isSliceFFClkMod() || bel.getName().endsWith("_IMR")) { Cell possibleRouteThru = inst.getCell(bel); - if (possibleRouteThru != null && possibleRouteThru.isRoutethru()) { - String routeThru = possibleRouteThru.getPinMappingsP2L().keySet().iterator().next(); - queue.add(bel.getPin(routeThru)); + if (possibleRouteThru == null) { + BELPin clkBelPin = bel.isSliceFFClkMod() ? bel.getPin("CLK") : null; + if (clkBelPin != null && inst.getNetFromSiteWire(clkBelPin.getSiteWireName()) == net) { + queue.add(clkBelPin); + } + } else { + if (possibleRouteThru.isRoutethru()) { + String routeThru = possibleRouteThru.getPinMappingsP2L().keySet().iterator().next(); + queue.add(bel.getPin(routeThru)); + } } } } @@ -2309,20 +2317,40 @@ public static List getAllRoutedSitePinsFromPhysicalPin(Cell cell, Net ne if (!siteWires.contains(sink.getSiteWireName())) continue; if (sink.isSitePort()) { sitePins.add(sink.getName()); - } else if (sink.getBEL().getBELClass() == BELClass.RBEL) { + continue; + } + BEL bel = sink.getBEL(); + if (bel.getBELClass() == BELClass.RBEL) { // Check if the SitePIP is being used - SitePIP sitePIP = inst.getUsedSitePIP(sink.getBELName()); - if (sitePIP == null) continue; - // Don't proceed if it's configured for a different pin - if (!sitePIP.getInputPinName().equals(sink.getName())) continue; + SitePIP sitePIP = inst.getUsedSitePIP(sink); + if (sitePIP == null) { + continue; + } + assert(sitePIP.getInputPinName().equals(sink.getName())); // Make this the new source to search from and keep looking... queue.add(sitePIP.getOutputPin()); - } else if (sink.getBEL().isFF()) { + } else if (bel.isFF()) { // FF pass thru option (not a site PIP) - siteWireName = sink.getBEL().getPin("Q").getSiteWireName(); + siteWireName = bel.getPin("Q").getSiteWireName(); if (siteWires.contains(siteWireName)) { sitePins.add(siteWireName); } + } else if (bel.getBELType().equals("DSP_CAS_DELAY")) { + // Versal only + SitePIP sitePIP = inst.getUsedSitePIP(sink); + if (sitePIP == null) { + continue; + } + assert(sitePIP.getInputPinName().equals(sink.getName())); + // For an unknown reason, it appears that the sitewire is not painted correctly ... + // Make this the new source to search from and keep looking... + // queue.add(sitePIP.getOutputPin()); + // ... so assume it is and workaround + BELPin source = sitePIP.getOutputPin(); + assert(source.getSiteConns().size() == 1); + BELPin port = source.getSiteConns().get(0); + assert(port.isSitePort()); + sitePins.add(port.getName()); } } } @@ -3244,53 +3272,67 @@ public static void createA1A6ToStaticNets(Design design) { } String belName = bel.getName(); - if ("SRL16E".equals(cell.getType()) || "SRLC32E".equals(cell.getType())) { - String pinName = belName.charAt(0) + "1"; - SitePinInst spi = si.getSitePinInst(pinName); - if (spi != null) { - assert(spi.getNet().isVCCNet()); + char fiveOrSix = belName.charAt(1); + if (fiveOrSix == '5') { + // Assume that only 5LUT can use O5 + assert(cell.getLogicalPinMapping("O5") != null || cell.isRoutethru()); + if (LUTTools.getCompanionLUTCell(cell) != null) { + // 5LUT is used, but 6LUT also exists; let the 6LUT deal with things continue; } - vccNet.createPin(pinName, si); - } + } else { + assert(fiveOrSix == '6'); - if (cell.getLogicalPinMapping("A6") != null) { - // A6 pin is being used by LUT - continue; + if ("SRLC32E".equals(cell.getType())) { + // For SRLC32Es, only the A1 needs to be tied to VCC + String pinName = belName.charAt(0) + "1"; + SitePinInst spi = si.getSitePinInst(pinName); + if (spi == null) { + vccNet.createPin(pinName, si); + } else { + assert(spi.getNet().isVCCNet()); + } + // A6 is needed as a logical pin + assert(cell.getLogicalPinMapping("A6") != null); + } + + if (cell.getLogicalPinMapping("A6") != null) { + // A6 pin is being used by LUT/SRL; no need to tie it to VCC + continue; + } } - char fiveOrSix = belName.charAt(1); - assert(fiveOrSix == '5' || fiveOrSix == '6'); Net staticNet = vccNet; - BEL lut6Bel = (fiveOrSix == '5') ? si.getBEL(belName.charAt(0) + "6LUT") : bel; Net a6Net = si.getNetFromSiteWire(lut6Bel.getPin("A6").getSiteWireName()); - // SRL16Es that have been transformed from SRLC32E require GND on their A6 pin - if (cell.getType().equals("SRL16E") && "SRLC32E".equals(cell.getPropertyValueString("XILINX_LEGACY_PRIM"))) { - staticNet = gndNet; - // Expect sitewire to be VCC and GND - if (!a6Net.isStaticNet()) { - throw new RuntimeException("ERROR: Site pin " + si.getSiteName() + "/" + belName.charAt(0) + "6 is not a static net"); + boolean expectGndNet = false; + if ("SRL16E".equals(cell.getType())) { + String pinName = belName.charAt(0) + "1"; + SitePinInst spi = si.getSitePinInst(pinName); + if (spi == null) { + vccNet.createPin(pinName, si); } - } else { - // Tie A6 to staticNet only if sitewire says so - if (a6Net != staticNet) { - continue; + + // SRL16Es that have been transformed from SRLC32E require GND on their A6 pin + if ("SRLC32E".equals(cell.getPropertyValueString("XILINX_LEGACY_PRIM"))) { + expectGndNet = true; + staticNet = gndNet; + // Expect sitewire to be VCC or GND + if (!a6Net.isStaticNet()) { + throw new RuntimeException("ERROR: Site pin " + si.getSiteName() + "/" + belName.charAt(0) + "6 is not a static net"); + } } } + // Tie A6 to staticNet only if sitewire says so + if (a6Net != staticNet && !expectGndNet) { + continue; + } + if (cell.getLogicalPinMapping("O5") != null) { // LUT output comes out on O5 - if (fiveOrSix == '5') { - // It's a 5LUT - if (si.getCell(belName.charAt(0) + "6LUT") != null) { - // But 6LUT exists; let the 6LUT deal with it - continue; - } - } else { - throw new RuntimeException("Assumption that only 5LUTs can use O5 failed here."); - } + assert(fiveOrSix == '5'); } else { if (fiveOrSix != '6') { // Assume that O6 is only driven by 6LUT, even though possible for 5LUT, unless @@ -3298,7 +3340,6 @@ public static void createA1A6ToStaticNets(Design design) { assert (cell.isRoutethru()); continue; } - assert(fiveOrSix == '6'); } @@ -3370,14 +3411,16 @@ public static void createCeSrRstPinsToVCC(Design design) { Net net = si.getNetFromSiteWire(sitePinName); if (net != null) { if (belPinName == CE) { + if (!net.isVCCNet()) { + continue; + } // CE: it is possible for sitewire to be assigned to a non VCC net, but a SitePinInst to not yet exist - assert(!net.isVCCNet()); - continue; } else { - // SR: it is possible for sitewire to be assigned the GND net, yet still be routed to VCC + assert(belPinName == SR); if (!net.isStaticNet()) { continue; } + // SR: it is possible for sitewire to be assigned the GND net, yet still be routed to VCC } } diff --git a/src/com/xilinx/rapidwright/device/IntentCode.java b/src/com/xilinx/rapidwright/device/IntentCode.java index 2b084c9c5..4e92c8726 100644 --- a/src/com/xilinx/rapidwright/device/IntentCode.java +++ b/src/com/xilinx/rapidwright/device/IntentCode.java @@ -158,6 +158,12 @@ public boolean isUltraScaleClockDistribution() { return NODE_GLOBAL_HDISTR == this || NODE_GLOBAL_VDISTR == this; } + public boolean isVersalClocking() { + return NODE_GLOBAL_HDISTR == this || NODE_GLOBAL_HDISTR_LOCAL == this || NODE_GLOBAL_HROUTE_HSR == this || + NODE_GLOBAL_VDISTR == this || NODE_GLOBAL_VDISTR_LVL2 == this || NODE_GLOBAL_VROUTE == this || + NODE_GLOBAL_GCLK == this || NODE_GLOBAL_LEAF == this || NODE_GLOBAL_BUFG == this; + } + private static final int SERIES7_START_IDX = 23; private static final int SERIES7_END_IDX = SERIES7_START_IDX + 33 - 1; diff --git a/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java b/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java index 49e8823c4..b00834840 100644 --- a/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java +++ b/src/com/xilinx/rapidwright/rwroute/GlobalSignalRouting.java @@ -444,15 +444,6 @@ public static void routeStaticNet(List pins, } IntentCode uphillIntentCode = uphillNode.getIntentCode(); - if (uphillIntentCode == IntentCode.NODE_CLE_CNODE && intentCode != IntentCode.NODE_CLE_CTRL) { - assert(isVersal); - // Only allow PIPs from NODE_CLE_CNODE to NODE_CLE_CTRL intent codes - // (NODE_CLE_NODEs can also be used to re-enter the INT tile --- do not allow this - // so that these precious resources are not consumed by the static router thereby - // blocking the signal router from using them) - continue; - } - switch(uphillIntentCode) { case NODE_GLOBAL_VDISTR: case NODE_GLOBAL_HROUTE: @@ -469,6 +460,20 @@ public static void routeStaticNet(List pins, case NODE_VLONG7: case NODE_VLONG12: continue; + case NODE_CLE_CNODE: + // Only allow PIPs from NODE_{CLE,INTF}_CNODE to NODE_{CLE,INTF}_CTRL intent codes + // (NODE_CLE_NODEs can also be used to re-enter the INT tile --- do not allow this + // so that these precious resources are not consumed by the static router thereby + // blocking the signal router from using them) + if (intentCode != IntentCode.NODE_CLE_CTRL) { + continue; + } + break; + case NODE_INTF_CNODE: + if (intentCode != IntentCode.NODE_INTF_CTRL) { + continue; + } + break; // VCC net should never need to use S/D/Q nodes ... case NODE_SINGLE: diff --git a/src/com/xilinx/rapidwright/rwroute/RWRoute.java b/src/com/xilinx/rapidwright/rwroute/RWRoute.java index c533c14af..acba903d4 100644 --- a/src/com/xilinx/rapidwright/rwroute/RWRoute.java +++ b/src/com/xilinx/rapidwright/rwroute/RWRoute.java @@ -326,32 +326,6 @@ protected void determineRoutingTargets() { // Wait for all outstanding RouteNodeGraph.preserveAsync() calls to complete routingGraph.awaitPreserve(); - - // On Versal only, reserve all uphills of NODE_(CLE|INTF)_CTRL sinks since - // their [BC]NODEs can also be used to reach NODE_INODEs --- not applying this - // heuristic can lead to avoidable congestion - if (routingGraph.isVersal) { - for (Connection connection : indirectConnections) { - RouteNode sinkRnode = connection.getSinkRnode(); - if (sinkRnode.getType() == RouteNodeType.EXCLUSIVE_SINK_BOTH) { - for (Node uphill : sinkRnode.getAllUphillNodes()) { - if (uphill.isTiedToVcc()) { - continue; - } - Net preservedNet = routingGraph.getPreservedNet(uphill); - if (preservedNet != null && preservedNet != connection.getNet()) { - continue; - } - assert((sinkRnode.getIntentCode() == IntentCode.NODE_CLE_CTRL && - (uphill.getIntentCode() == IntentCode.NODE_CLE_CNODE || uphill.getIntentCode() == IntentCode.NODE_CLE_BNODE)) || - (sinkRnode.getIntentCode() == IntentCode.NODE_INTF_CTRL && - (uphill.getIntentCode() == IntentCode.NODE_INTF_CNODE || uphill.getIntentCode() == IntentCode.NODE_INTF_BNODE))); - RouteNode rnode = routingGraph.getOrCreate(uphill, RouteNodeType.LOCAL_RESERVED); - rnode.setType(RouteNodeType.LOCAL_RESERVED); - } - } - } - } } private void categorizeNets() { @@ -804,6 +778,32 @@ private void initializeRouting() { oneMinusTimingWeight = 1 - timingWeight; oneMinusWlWeight = 1 - wlWeight; printIterationHeader(config.isTimingDriven()); + + // On Versal only, reserve all uphills of NODE_(CLE|INTF)_CTRL sinks since + // their [BC]NODEs can also be used to reach NODE_INODEs --- not applying this + // heuristic can lead to avoidable congestion + if (routingGraph.isVersal) { + for (Connection connection : indirectConnections) { + RouteNode sinkRnode = connection.getSinkRnode(); + if (sinkRnode.getType() == RouteNodeType.EXCLUSIVE_SINK_BOTH) { + for (Node uphill : sinkRnode.getAllUphillNodes()) { + if (uphill.isTiedToVcc()) { + continue; + } + Net preservedNet = routingGraph.getPreservedNet(uphill); + if (preservedNet != null && preservedNet != connection.getNet()) { + continue; + } + assert((sinkRnode.getIntentCode() == IntentCode.NODE_CLE_CTRL && + (uphill.getIntentCode() == IntentCode.NODE_CLE_CNODE || uphill.getIntentCode() == IntentCode.NODE_CLE_BNODE)) || + (sinkRnode.getIntentCode() == IntentCode.NODE_INTF_CTRL && + (uphill.getIntentCode() == IntentCode.NODE_INTF_CNODE || uphill.getIntentCode() == IntentCode.NODE_INTF_BNODE))); + RouteNode rnode = routingGraph.getOrCreate(uphill, RouteNodeType.LOCAL_RESERVED); + rnode.setType(RouteNodeType.LOCAL_RESERVED); + } + } + } + } } /** diff --git a/src/com/xilinx/rapidwright/rwroute/RouteNode.java b/src/com/xilinx/rapidwright/rwroute/RouteNode.java index a5fb5af8c..d4391f7a9 100644 --- a/src/com/xilinx/rapidwright/rwroute/RouteNode.java +++ b/src/com/xilinx/rapidwright/rwroute/RouteNode.java @@ -165,6 +165,15 @@ private void setBaseCost(Series series) { break; case NODE_VSINGLE: // Versal-only case NODE_HSINGLE: // Versal-only + if (length == 0 && getAllWiresInNode().length == 1) { + assert(getAllDownhillPIPs().isEmpty() || // e.g. INT_X3Y383/OUT_NN1_E_BEG6 and INT_X19Y384/OUT_EE1_E_BEG8 on vp1002 + (ic == IntentCode.NODE_HSINGLE && getWireName().startsWith("INT_SDQ_"))); + // HSINGLE nodes that have a wirename INT_SDQ_* do not travel to any other + // tiles but still have downhill PIPs (and thus we cannot mark as being + // inaccessible without checking getAllDownhillPIPs() or getWireName()) + break; + } + // Fall through case NODE_SINGLE: // US and US+ if (length <= 1) { assert(!getAllDownhillPIPs().isEmpty()); @@ -175,6 +184,14 @@ private void setBaseCost(Series series) { break; case NODE_VDOUBLE: // Versal only case NODE_HDOUBLE: // Versal only + if (length == 0 && getAllWiresInNode().length == 1) { + // e.g. INT_X2Y382/OUT_NN2_W_BEG2 and INT_X18Y384/OUT_WW2_W_BEG4 on vp1002 + assert(getAllDownhillPIPs().isEmpty()); + // This node has no downhill PIPs, mark these as inaccessible so that it will never be queued + type = (byte) RouteNodeType.INACCESSIBLE.ordinal(); + break; + } + // Fall through case NODE_DOUBLE: // US and US+ if (length == 0) { assert(!getAllDownhillPIPs().isEmpty()); @@ -194,9 +211,9 @@ private void setBaseCost(Series series) { } } break; - case NODE_HQUAD: + case NODE_HQUAD: // US/US+/Versal if (length == 0) { - // Since this node has zero length (and by extension no downhill PIPs) + // Since this node has zero length (and asserted to have no downhill PIPs) // mark it as being inacccessible so that it will never be queued assert(getAllDownhillPIPs().isEmpty()); type = (byte) RouteNodeType.INACCESSIBLE.ordinal(); @@ -204,9 +221,11 @@ private void setBaseCost(Series series) { baseCost = 0.35f * length; } break; - case NODE_VQUAD: + case NODE_VQUAD: // US/US+/Versal if (length == 0) { - assert(!getAllDownhillPIPs().isEmpty()); + // On Versal, INT_X1Y380/OUT_NN4_W_BEG6 on vp1002 has no downhill PIPs + assert((series == Series.Versal && getAllWiresInNode().length == 1) || + !getAllDownhillPIPs().isEmpty()); } else { // VQUADs have length 4 and 5 baseCost = 0.15f * length; @@ -219,7 +238,7 @@ private void setBaseCost(Series series) { break; case NODE_HLONG: // US/US+ if (length == 0) { - // Since this node has zero length (and by extension no downhill PIPs) + // Since this node has zero length (and asserted to have no downhill PIPs) // mark it as being inacccessible so that it will never be queued assert(getAllDownhillPIPs().isEmpty()); type = (byte) RouteNodeType.INACCESSIBLE.ordinal(); diff --git a/src/com/xilinx/rapidwright/rwroute/RouterHelper.java b/src/com/xilinx/rapidwright/rwroute/RouterHelper.java index 1f61cf225..4eea6d50d 100644 --- a/src/com/xilinx/rapidwright/rwroute/RouterHelper.java +++ b/src/com/xilinx/rapidwright/rwroute/RouterHelper.java @@ -53,6 +53,7 @@ import com.xilinx.rapidwright.device.Node; import com.xilinx.rapidwright.device.PIP; import com.xilinx.rapidwright.device.Series; +import com.xilinx.rapidwright.device.SiteTypeEnum; import com.xilinx.rapidwright.device.Tile; import com.xilinx.rapidwright.device.TileTypeEnum; import com.xilinx.rapidwright.edif.EDIFHierCellInst; @@ -350,7 +351,7 @@ public static Set invertPossibleGndPinsToVccPins(Design design, } Collection connectedCells = DesignTools.getConnectedCells(spiBelPin, si); if (connectedCells.isEmpty()) { - for (BELPin belPin : si.getSiteWirePins(siteWireName)) { + for (BELPin belPin : si.getSiteWirePins(spiBelPin.getSiteWireName())) { if (belPin.isSitePort()) { continue; } @@ -410,7 +411,12 @@ public static Set invertPossibleGndPinsToVccPins(Design design, } else { BELPin[] belPins = si.getSiteWirePins(siteWireName); if (belPins.length != 2) { - continue; + if (belPins.length == 3 && si.getSiteTypeEnum() == SiteTypeEnum.DSP58 && siteWireName.equals("RSTD")) { + assert(isVersal); + assert(belPins[1].toString().equals("SRCMXINV.RSTAD_UNUSED")); + } else { + continue; + } } for (BELPin belPin : belPins) { if (belPin.isSitePort()) { @@ -421,7 +427,8 @@ public static Set invertPossibleGndPinsToVccPins(Design design, } // Emulate Vivado's behaviour and do not invert CLK* site pins if (Utils.isBRAM(spi.getSiteInst()) && - belPin.getBELName().startsWith("CLK")) { + belPin.getBELName().startsWith("CLK") && + !isVersal) { continue; } toInvertPins.add(spi); diff --git a/test/src/com/xilinx/rapidwright/device/TestNode.java b/test/src/com/xilinx/rapidwright/device/TestNode.java index eacef5bd4..a180fe26b 100644 --- a/test/src/com/xilinx/rapidwright/device/TestNode.java +++ b/test/src/com/xilinx/rapidwright/device/TestNode.java @@ -237,7 +237,8 @@ public void testNodeReachabilityUltraScale(String partName, String tileName, Str "xcvp1002,INT_X38Y220,NODE_IMUX,IMUX_B_E.*,true", "xcvp1002,INT_X38Y220,NODE_IMUX,IMUX_B_W.*,true", "xcvp1002,INT_X38Y220,NODE_SDQNODE,,false", - "xcvp1002,INT_X38Y220,NODE_HSINGLE,,false", + "xcvp1002,INT_X38Y220,NODE_HSINGLE,OUT_.*,false", + "xcvp1002,INT_X38Y220,NODE_HSINGLE,INT_SDQ_RED_ATOM_.*,false", "xcvp1002,INT_X38Y220,NODE_VSINGLE,,false", "xcvp1002,INT_X38Y220,NODE_HDOUBLE,,false", "xcvp1002,INT_X38Y220,NODE_VDOUBLE,,false", From 0e13fbe7d198a3695373384de8144a3490a78832 Mon Sep 17 00:00:00 2001 From: Wenhao Lin Date: Wed, 27 Nov 2024 09:50:13 +0800 Subject: [PATCH 3/3] [RWRoute] Clock router for Versal architecture (#1102) * Support clock routing on Versal devices Signed-off-by: Wenhao Lin * Add licence Signed-off-by: Wenhao Lin * testNodeReachabilityVersal to examine some NODE_GLOBAL_LEAF wires Signed-off-by: Eddie Hung * Fix bugs in mapping sinks to LCBs Signed-off-by: Wenhao Lin * Add clock routing test on versal devices Signed-off-by: Wenhao Lin * Route backward from sinks to LCBs and update test Signed-off-by: Wenhao Lin * revert getLCBPinMappings for US/US+ devices Signed-off-by: Wenhao Lin * Modify testNodeReachabilityVersal Signed-off-by: Wenhao Lin * Add TestGlobalSignalRouting.testSymmetricClkRouting() Signed-off-by: Eddie Hung * Fix testSymmetricClkRouting Signed-off-by: Wenhao Lin * Do not use removed method Signed-off-by: Eddie Hung * Small cleanup Signed-off-by: Eddie Hung * Fix testSymmetricClkRouting Signed-off-by: Wenhao Lin * Check nets are routed Signed-off-by: Eddie Hung * Refactor Signed-off-by: Eddie Hung * VersalClockRouting.routeToCentroid() always has adjusted=true Signed-off-by: Eddie Hung * More refactoring Signed-off-by: Eddie Hung * Remove println()-s Signed-off-by: Eddie Hung * Cleanup comments Signed-off-by: Eddie Hung * More tidying Signed-off-by: Eddie Hung * First phase of cleanup; do not pass RouteNode between methods Signed-off-by: Eddie Hung * [VersalClockRouting] Rewrite without RouteNode Signed-off-by: Eddie Hung * More tidying up Signed-off-by: Eddie Hung * Even more tidying Signed-off-by: Eddie Hung * Last bit of tidying Signed-off-by: Eddie Hung * Add Javadoc Signed-off-by: Wenhao Lin * Apply suggestions from code review Co-authored-by: Chris Lavin Signed-off-by: eddieh-xlnx * Address review comments Signed-off-by: Eddie Hung --------- Signed-off-by: Wenhao Lin Signed-off-by: Eddie Hung Signed-off-by: eddieh-xlnx Co-authored-by: Eddie Hung Co-authored-by: Chris Lavin --- .../router/UltraScaleClockRouting.java | 2 +- .../router/VersalClockRouting.java | 495 ++++++++++++++++++ .../rwroute/GlobalSignalRouting.java | 97 +++- .../rapidwright/rwroute/RouterHelper.java | 47 +- .../xilinx/rapidwright/device/TestNode.java | 3 +- .../rwroute/TestGlobalSignalRouting.java | 36 ++ 6 files changed, 645 insertions(+), 35 deletions(-) create mode 100644 src/com/xilinx/rapidwright/router/VersalClockRouting.java 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"); + } + } }