/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: AStarRouter.java * Written by: Christian Harnisch, Ingo Besenfelder, Michael Neumann (Team 3) * * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * * Electric(tm) is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Electric(tm) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ package com.sun.electric.tool.routing.experimentalAStar3; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import com.sun.electric.database.hierarchy.Cell; import com.sun.electric.database.topology.NodeInst; import com.sun.electric.tool.Job; import com.sun.electric.tool.routing.RoutingFrame.RoutingSegment; import com.sun.electric.tool.routing.experimentalAStar3.concurrency.RouteJob; import com.sun.electric.tool.routing.experimentalAStar3.concurrency.RoutingMain; import com.sun.electric.tool.routing.experimentalAStar3.datastructures.Point3D; /** * @author Christian Harnisch * */ public class AStarRouter extends BenchmarkRouter { /* Tuning parameters */ /** * Number of regions per side. Total number of regions == numRegionsPerSide * * numRegionsPerSide. */ public RoutingParameter numRegionsPerSide = new RoutingParameter("regionsps", "Number of regions per side:", 8); public AStarRouter() { super(); setBenchmarkParameters(16, 60, 2); } public void setBenchmarkParameters(int threads, int runtime, int numregionsperside) { this.numThreads.setTempIntValue(threads); this.maxRuntime.setTempIntValue(runtime); this.numRegionsPerSide.setTempIntValue(numregionsperside); } /* Below: Private members, not for tuning. */ RoutingMain router; /** * Depicts the size of one node on the actual cell. This value is calculated * by the private method <code>calculateNodeSize</code> on initialisation of * the AStarRouter, so don't set it here. */ private double nodeSize; /* * Used to translate node coordinates. */ private int nodeOffsetX = 0; private int nodeOffsetY = 0; @Override public String getAlgorithmName() { return "A* - 3"; //$NON-NLS-1$ } /** * Returns the next power of 2 */ private int nextPowerOf2(int value) { assert (value > 0); int x = 1; while (value > x) x *= 2; return x; } /** * Calculates the length of the sides of a node on the cell the router finds * paths on. * * @param allLayers All layers of the cell. */ private double calculateNodeSize(List<RoutingLayer> allLayers) { double maxMinWidth = 0; for (RoutingLayer layer : allLayers) { if (maxMinWidth < layer.getMinWidth() / 2 + layer.getMinSpacing(layer)) maxMinWidth = layer.getMinSpacing(layer) + layer.getMinWidth() / 2; } return maxMinWidth * 2; } /* Small helpers for unit conversion */ /** * This method can be used for converting coordinates from the cell to node * coordinates, or other similar unit conversions. * * @param c * @return */ private int cellUnitToNode(double c) { return (int) Math.floor(c / this.getNodeSize()); } // /** // * This method can be used for converting node coordinates to cell // * coordinates, or other similar unit conversions. // * // * @param n // * @return // */ // private double nodeToCellUnit(int n) // { // return (double) (n * this.getNodeSize()); // } private int cellCoordXToNode(double c) { return cellUnitToNode(c) + this.nodeOffsetX; } private int cellCoordYToNode(double c) { return cellUnitToNode(c) + this.nodeOffsetY; } public double nodeCoordXToCellCentered(int n) { return this.getNodeSize() * (n - this.nodeOffsetX) + this.getNodeSize() / 2; } public double nodeCoordYToCellCentered(int n) { return this.getNodeSize() * (n - this.nodeOffsetY) + this.getNodeSize() / 2; } public double nodeCoordXToCell(int n) { return this.getNodeSize() * (n - this.nodeOffsetX); } public double nodeCoordYToCell(int n) { return this.getNodeSize() * (n - this.nodeOffsetY); } private RoutingLayer getLayerFromZ(int z) { return this.allLayers.get(z); } private int getZFromLayer(RoutingLayer layer) { return layer.getMetalNumber() - 1; } private List<RoutingLayer> allLayers; private List<RoutingContact> allContacts; // private void checkMetalLayers() // { // int lowestMetalLayer = Integer.MAX_VALUE; // int highestMetalLayer = -1; // int currentMetalLayer = -1; // int metalLayerCount = 0; // boolean hasSeenNonMetal = false; // for (RoutingLayer layer : this.allLayers) // { // if (layer.isMetal()) // { // metalLayerCount++; // if (!hasSeenNonMetal) // { // if (layer.getMetalNumber() > currentMetalLayer) // currentMetalLayer = layer.getMetalNumber(); // else // throw new IllegalStateException("Metal layers unsorted!"); // if (currentMetalLayer < lowestMetalLayer) // lowestMetalLayer = currentMetalLayer; // if (currentMetalLayer > highestMetalLayer) // highestMetalLayer = currentMetalLayer; // } // else // throw new IllegalStateException("Metal layers below non-metal layers!"); // } // else // hasSeenNonMetal = true; // } // System.out.println("Metal layers: count=" + metalLayerCount + ", lowest=" + lowestMetalLayer + ", highest=" // + highestMetalLayer + ", total layers=" + this.allLayers.size()); // } @Override protected void runRouting(Cell cell, List<RoutingSegment> segmentsToRoute, List<RoutingLayer> allLayers, List<RoutingContact> allContacts, List<RoutingGeometry> blockages) { // Store start time for maximum runtime calculation long startTime = System.currentTimeMillis(); /* Initialisation phase */ this.allLayers = allLayers; this.allContacts = allContacts; // User-interface initialization Job.getUserInterface().startProgressDialog("Routing " + segmentsToRoute.size() + " segments", null); Job.getUserInterface().setProgressNote("Initialising..."); Job.getUserInterface().setProgressValue(0); this.setNodeSize(calculateNodeSize(allLayers)); // System.out.println("Node Size: " + this.getNodeSize()); // // Determine bounds // int min_x = cellUnitToNode(cell.getBounds().getMinX()); int min_y = cellUnitToNode(cell.getBounds().getMinY()); int max_x = cellUnitToNode(cell.getBounds().getMaxX()); int max_y = cellUnitToNode(cell.getBounds().getMaxY()); final int border = 10; int real_width = max_x - min_x + 1; int real_height = max_y - min_y + 1; int width = real_width + 2 * border; int height = real_height + 2 * border; int quadlen = nextPowerOf2((width > height) ? width : height); // XXX: we need a minimum width, otherwise we can't divide it well if (quadlen < 128) quadlen = 128; // // Node coordinate translation happens here. // this.nodeOffsetX = -min_x + (quadlen - real_width) / 2; // move into center this.nodeOffsetY = -min_y + (quadlen - real_height) / 2; // move into center // System.out.println("" + min_x + "/" + min_y + " -- " + max_x + "/" + // max_y); // System.out.println("" + quadlen); this.router = new RoutingMain(quadlen, quadlen, getMetalLayerCount(allLayers), this.numRegionsPerSide.getTempIntValue(), this.numThreads.getTempIntValue()); for (RoutingGeometry routingGeometry : blockages) { Rectangle2D bounds = routingGeometry.getBounds(); int startX = cellCoordXToNode(bounds.getX()); int startY = cellCoordYToNode(bounds.getY()); int endX = cellCoordXToNode(Math.ceil(bounds.getX() + bounds.getWidth())); int endY = cellCoordYToNode(Math.ceil(bounds.getY() + bounds.getHeight())); this.router.setBlockage(startX, startY, endX, endY, getZFromLayer(routingGeometry.getLayer())); // RoutingSegment routingSegment = segmentsToRoute.get(0); // RoutingLayer layer = routingSegment.getFinishLayers().get(0); // RoutePoint rp = new RoutePoint(layer.getPin(), new // Point2D.Double(nodeCoordXToCell(startX), nodeCoordYToCell(startY)), 0); // routingSegment.addWireEnd(rp); // RoutePoint rp2 = new RoutePoint(layer.getPin(), new // Point2D.Double(nodeCoordXToCell(endX), nodeCoordYToCell(endY)), 0); // routingSegment.addWireEnd(rp2); // routingSegment.addWire(new RouteWire(layer, rp, rp2, 2)); } // Place portals on region borders. this.router.placePortals(); // Sort segments according to their expected length. This is good because 1) // with limited time, we can finish more paths successfully and 2) this // improves path quality since short paths don't make many detours. Collections.sort(segmentsToRoute, new SegmentComparator()); /* End of init phase */ Job.getUserInterface().setProgressNote("Scheduling segments to route..."); for (RoutingSegment rs : segmentsToRoute) { RoutePoint rpSTART = new RoutePoint(RoutingContact.STARTPOINT, rs.getStartEnd().getLocation(), 0); RoutePoint rpEND = new RoutePoint(RoutingContact.FINISHPOINT, rs.getFinishEnd().getLocation(), 0); Point3D ptSTART = new Point3D(cellCoordXToNode(rpSTART.getLocation().getX()), cellCoordYToNode(rpSTART.getLocation().getY()), getZFromLayer(rs.getStartLayers().get(0))); Point3D ptEND = new Point3D(cellCoordXToNode(rpEND.getLocation().getX()), cellCoordYToNode(rpEND.getLocation().getY()), getZFromLayer(rs.getFinishLayers().get(0))); // System.out.print("Submit job: Segment (" + // (rpSTART.getLocation().getX()) + "/" + (rpSTART.getLocation().getY()) + // ")->(" // + (rpEND.getLocation().getX()) + "/" + (rpEND.getLocation().getY()) + // ")"); // System.out.print(" = Node-Route (" + (ptSTART.getX()) + "/" + // (ptSTART.getY()) + ")"); // System.out.println("->(" + (ptEND.getX()) + "/" + (ptEND.getY()) + // ")"); MyRouteJob job = new MyRouteJob(ptSTART, ptEND, this); job.routingSegment = rs; this.router.submitRouteJob(job); } // Now that all jobs are submitted, // we're ready to let the router do its work. Job.getUserInterface().setProgressNote("Routing segments..."); // System.out.println("waitForCompletion"); // After initialising and submitting jobs (which took time, too), use the // rest of the maxRuntime for routing. // XXX [fschmidt] check timeout here long restTime = (this.maxRuntime.getIntValue() * 1000) - (System.currentTimeMillis() - startTime); this.router.setMaxRuntimeMillis(restTime); this.router.waitForCompletion(); Job.getUserInterface().stopProgressDialog(); } private int getMetalLayerCount(List<RoutingLayer> allLayers) { int highestMetalNumber = 0; for (RoutingLayer routingLayer : allLayers) { if (routingLayer.isMetal()) { // System.out.println("Layer " + routingLayer.getMetalNumber() + " Via Spacing: " + routingLayer.getPin().getViaSpacing() // + " Wire Width: " + routingLayer.getMinWidth() + " MinSpacing: " + routingLayer.getMinWidth()); highestMetalNumber = Math.max(highestMetalNumber, routingLayer.getMetalNumber()); } } return highestMetalNumber; } synchronized void routeJobCompleted(RouteJob job) { Job.getUserInterface().setProgressValue(this.router.getProgress()); List<Point3D> path = job.getPath(); if (path != null) { RoutingSegment segment = job.routingSegment; // if (checkOnAngularPointsInPath(path)) // System.err.println("AStarRouter: Path contains angular lines BEFORE converting! (" // + job.from.getX() + "," // + job.from.getY() + ") -> (" + job.to.getX() + "," + job.to.getY() + // ")"); path = optimizePath(path); PathConversionData data = new PathConversionData(); List<RoutePoint> routePoints = convertPathToRoutePoints(path, segment, data); // if (checkOnAngularRoutePoints(routePoints)) // System.err.println("AStarRouter: Path contains angular lines AFTER converting! (" // + job.from.getX() + "," // + job.from.getY() + ") -> (" + job.to.getX() + "," + job.to.getY() + // ")"); RoutingLayer layer; /* Really short path */ if (path.size() == 1) { // Use finish layer, because path could switch layers right after the // first point layer = segment.getFinishLayers().get(0); segment.addWireEnd(routePoints.get(0)); segment.addWireEnd(routePoints.get(1)); segment.addWireEnd(routePoints.get(2)); segment.addWire(new RouteWire(segment.getStartLayers().get(0), routePoints.get(0), routePoints.get(1), layer .getMinWidth())); segment.addWire(new RouteWire(layer, routePoints.get(1), routePoints.get(2), layer.getMinWidth())); return; } /* * Connect the start points (including the adapted points). These are * definitely on the same layer. */ layer = segment.getStartLayers().get(0); // The conversion adds at least one point at the front of the path, so // this is safe to do. segment.addWireEnd(routePoints.get(0)); segment.addWireEnd(routePoints.get(1)); segment.addWire(new RouteWire(layer, routePoints.get(0), routePoints.get(1), layer.getMinWidth())); // When the conversion added another point at the front of the path, add // the third route point as well. if (data.startPointsAdded == 1) { segment.addWireEnd(routePoints.get(2)); segment.addWire(new RouteWire(layer, routePoints.get(1), routePoints.get(2), layer.getMinWidth())); } /* * Connect the points in between start and end points. */ for (int i = 0; i < (path.size() - 1); i++) { if (path.get(i).getZ() != path.get(i + 1).getZ()) { layer = getLayerFromZ(path.get(i + 1).getZ()); /* * int currentRoutePointIndex = i + 2 + data.startPointsAdded; * RoutingLayer startLayer = getLayerFromZ(path.get(i).getZ()); * RoutingLayer finishLayer = getLayerFromZ(path.get(i+1).getZ()); * RoutingContact viaContact = getVia(startLayer, finishLayer); * * RoutePoint startPoint = routePoints.get(currentRoutePointIndex - * 1); Point2D.Double viaCoords = new * Point2D.Double(startPoint.getLocation().getX(), * startPoint.getLocation().getY()); // XXX: Coordinates of viaPoint * RoutePoint viaPoint = new RoutePoint(viaContact, viaCoords, * segment.getNetID()); RoutePoint finishPoint = * routePoints.get(currentRoutePointIndex); * * RouteWire start_via = new RouteWire(startLayer, startPoint, * viaPoint, startLayer.getMinWidth()); RouteWire via_finish = new * RouteWire(finishLayer, viaPoint, finishPoint, * finishLayer.getMinWidth()); * * // startPoint has already been added as WireEnd in the last * iteration of the for loop segment.addWireEnd(finishPoint); * segment.addWireEnd(viaPoint); * * segment.addWire(start_via); segment.addWire(via_finish); */ } else { // No change in layer layer = getLayerFromZ(path.get(i).getZ()); /* * int currentRoutePointIndex = i + 2 + data.startPointsAdded; * segment.addWireEnd(routePoints.get(currentRoutePointIndex)); * segment.addWire(new RouteWire(layer, * routePoints.get(currentRoutePointIndex - 1), * routePoints.get(currentRoutePointIndex), layer.getMinWidth())); */ } int currentRoutePointIndex = i + 2 + data.startPointsAdded; segment.addWireEnd(routePoints.get(currentRoutePointIndex)); segment.addWire(new RouteWire(layer, routePoints.get(currentRoutePointIndex - 1), routePoints.get(currentRoutePointIndex), layer.getMinWidth())); } /* * Connect the end points (including the adapted points). The end points * are on the same layer, too. */ // When the conversion added another point at the end of the path, add // the third last route point as well. if (data.endPointsAdded == 1) { segment.addWireEnd(routePoints.get(routePoints.size() - 2)); segment.addWire(new RouteWire(layer, routePoints.get(routePoints.size() - 3), routePoints.get(routePoints.size() - 2), layer.getMinWidth())); } // The conversion adds at least one point at the end of the path, so // this is safe to do as well. layer = segment.getFinishLayers().get(0); // segment.addWireEnd(routePoints.get(routePoints.size() - 2)); segment.addWireEnd(routePoints.get(routePoints.size() - 1)); segment.addWire(new RouteWire(layer, routePoints.get(routePoints.size() - 2), routePoints.get(routePoints.size() - 1), layer.getMinWidth())); } } public RoutingContact getVia(RoutingLayer l1, RoutingLayer l2) { for (RoutingContact rc : this.allContacts) { if ((rc.getFirstLayer() == l1 && rc.getSecondLayer() == l2) || (rc.getFirstLayer() == l2 && rc.getSecondLayer() == l1)) { return rc; } } throw new IllegalArgumentException("Between the layers " + l1.getName() + " and " + l2.getName() + ", no via contact could be found."); } // private boolean checkOnAngularRoutePoints(List<RoutePoint> routePoints) // { // RoutePoint last = null; // for (RoutePoint routePoint : routePoints) // { // if (last != null) // if ((routePoint.getLocation().getX() != last.getLocation().getX() && routePoint.getLocation().getY() != last // .getLocation().getY())) // // || (segment.getStartLayers().get(0)!= // // last.getContact().getFirstLayer() && // // routePoint.getLocation().getY() != last // // .getLocation().getY()) // // || (routePoint.getLocation().getX() != last.getLocation().getX() && // // routePoint.getContact().getFirstLayer() != last // // .getContact().getFirstLayer())) // return true; // last = routePoint; // } // return false; // } // private boolean checkOnAngularPointsInPath(List<Point3D> path) // { // Point3D last = null; // for (Point3D point : path) // { // // if (point.getZ() != 0) // // System.err.println("Used layer " + point.getZ()); // if (last != null) // if ((point.getX() != last.getX() && point.getY() != last.getY()) // || (point.getX() != last.getX() && point.getZ() != last.getZ()) // || (point.getY() != last.getY() && point.getZ() != last.getZ())) // return true; // last = point; // } // return false; // } private List<Point3D> optimizePath(List<Point3D> path) { if (path.size() < 3) return path; List<Point3D> result = new ArrayList<Point3D>(); Point3D last = null; Point3D previousToLast = null; result.add(path.get(0)); int i = 0; for (Point3D cur : path) { if (last != null) { if (last.getZ() != cur.getZ() && (i > 1)) { result.add(last); } if (previousToLast != null) if ((previousToLast.getX() != cur.getX()) && (previousToLast.getY() != cur.getY())) { result.add(last); } } // if (checkOnAngularPointsInPath(result)) // System.out.println("AStarRouter: Wrong conversion or true angular path detected! Path-ID:" // + path.hashCode()); previousToLast = last; last = cur; ++i; } if (path.get(path.size() - 2).getZ() == path.get(path.size() - 1).getZ()) result.add(path.get(path.size() - 1)); // for (Point3D cur : result) // { // if (last != null) // { // if (last.getZ() == cur.getZ()) // if (last.getY() == cur.getY()) // if (last.getX() == cur.getX()) // { // System.out.println("Warning: Duplicate points!"); // for (Point3D current : result) // System.out.print(current.toString() + " "); // System.out.print("\n"); // } // // } // last = cur; // } return result; } /** * * @param path - the list of nodes in reversed order (the result list of the * route() method after backtracking) * @param layer - the layer to operate on. * @return the list of RoutePoints in correct order from start to destination * cell */ private List<RoutePoint> convertPathToRoutePoints(List<Point3D> path, RoutingSegment segment, PathConversionData data) { List<RoutePoint> result = new ArrayList<RoutePoint>(); // Start Point is always needed RoutePoint startPoint = new RoutePoint(RoutingContact.STARTPOINT, segment.getStartEnd().getLocation(), 0); result.add(startPoint); // End Point is always needed RoutePoint endPoint = new RoutePoint(RoutingContact.FINISHPOINT, segment.getFinishEnd().getLocation(), 0); RoutePoint smoothedEndPoint = null; Point3D first = null; Point3D second = null; if (path.size() > 1) { first = path.get(0); second = path.get(1); // Horizontal path starts here: Insert point on same y but with adapted x if (first.getX() != second.getX()) { result.add(new RoutePoint(segment.getStartLayers().get(0).getPin(), new Point2D.Double(startPoint.getLocation().getX(), nodeCoordYToCellCentered(first.getY())), 0)); data.startPointsAdded = 1; } // Vertical path starts here: Insert point on same x but with adapted y else // if (first.getY() != second.getY()) { result.add(new RoutePoint(segment.getStartLayers().get(0).getPin(), new Point2D.Double(nodeCoordXToCellCentered(first .getX()), startPoint.getLocation().getY()), 0)); data.startPointsAdded = 1; } Point3D last = null; for (Point3D cur : path) { RoutingContact pin = null; // If the two points reside on different layers, use a special via // contact. if (last != null) { if (last.getZ() != cur.getZ()) pin = getVia(getLayerFromZ(last.getZ()), getLayerFromZ(cur.getZ())); else pin = getLayerFromZ(last.getZ()).getPin(); result.add(new RoutePoint(pin, new Point2D.Double(nodeCoordXToCellCentered(last.getX()), nodeCoordYToCellCentered(last .getY())), 0)); // Special case: Last point if ((cur == path.get(path.size() - 1)) && (getLayerFromZ(cur.getZ()) != segment.getFinishLayers().get(0))) { pin = getVia(getLayerFromZ(cur.getZ()), segment.getFinishLayers().get(0)); result.add(new RoutePoint(pin, new Point2D.Double(nodeCoordXToCellCentered(cur.getX()), nodeCoordYToCellCentered(cur .getY())), 0)); } } // if (checkOnAngularRoutePoints(result)) // System.out.println("AStarRouter: Wrong conversion or true angular path detected! Path-ID:" // + path.hashCode()); last = cur; } first = path.get(path.size() - 2); second = path.get(path.size() - 1); // Horizontal path ends here: Insert point on same y but with adapted x if (first.getX() != second.getX()) { smoothedEndPoint = new RoutePoint(segment.getFinishLayers().get(0).getPin(), new Point2D.Double(endPoint.getLocation() .getX(), nodeCoordYToCellCentered(first.getY())), 0); data.endPointsAdded = 1; } // Vertical path ends here: Insert point on same x but with adapted y else // if (first.getY() != second.getY()) { smoothedEndPoint = new RoutePoint(segment.getFinishLayers().get(0).getPin(), new Point2D.Double( nodeCoordXToCellCentered(first.getX()), endPoint.getLocation().getY()), 0); data.endPointsAdded = 1; } } else { smoothedEndPoint = new RoutePoint(segment.getFinishLayers().get(0).getPin(), new Point2D.Double(endPoint.getLocation() .getX(), startPoint.getLocation().getY()), 0); data.endPointsAdded = 1; } if (smoothedEndPoint != null) result.add(smoothedEndPoint); result.add(endPoint); return result; } public void setNodeSize(double nodeSize) { this.nodeSize = nodeSize; } public double getNodeSize() { return nodeSize; } } class MyRouteJob extends RouteJob { public MyRouteJob(Point3D from, Point3D to, AStarRouter astarrouter) { super(from, to, astarrouter); } public void onCompletion() { this.astarrouter.routeJobCompleted(this); } } class SegmentComparator implements Comparator<RoutingSegment> { /* * (non-Javadoc) * * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ public int compare(RoutingSegment arg0, RoutingSegment arg1) { double length0 = arg0.getStartEnd().getLocation().distance(arg0.getFinishEnd().getLocation()); double length1 = arg1.getStartEnd().getLocation().distance(arg1.getFinishEnd().getLocation()); if (length0 < length1) return -1; else if (length0 == length1) return 0; else return 1; } } class PathConversionData { public int startPointsAdded = 0; public int endPointsAdded = 0; }