From 784bad2e99160ad5991e4c2baff58e4c8e50a4b3 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 16 Nov 2016 16:47:31 -0800 Subject: [PATCH] Routing multilayer, around blockages. --- compiler/router/cell.py | 16 +- compiler/router/grid.py | 132 +++++++++++++++- compiler/router/router.py | 27 ++-- compiler/router/tests/01_no_blockages_test.py | 4 +- compiler/router/vector3d.py | 145 ++++++++++++++++++ 5 files changed, 292 insertions(+), 32 deletions(-) create mode 100644 compiler/router/vector3d.py diff --git a/compiler/router/cell.py b/compiler/router/cell.py index 7b93e116..e2bcca9c 100644 --- a/compiler/router/cell.py +++ b/compiler/router/cell.py @@ -5,28 +5,26 @@ class cell: A single cell that can be occupied in a given layer, blocked, visited, etc. """ - scale=1 - def __init__(self): - self.visited = 0 + self.path = False self.blocked = False - self.is_source = False - self.is_target = False + self.source = False + self.target = False def get_color(self): # Blues are horizontal if self.blocked: - return ImageColor.getrgb("Blue") + return ImageColor.getrgb("Green") # Reds are source/sink - if self.is_source or self.is_target: + if self.source or self.target: return ImageColor.getrgb("Red") - if self.visited>0: - return [255-min(int(self.visited/cell.scale * 255),255)] * 3 + if self.path: + return ImageColor.getrgb("Blue") return [255,255,255] diff --git a/compiler/router/grid.py b/compiler/router/grid.py index 53dca715..e01d858e 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -1,8 +1,14 @@ import numpy as np from PIL import Image import debug +from vector3d import vector3d from cell import cell +try: + import Queue as Q # ver. < 3.0 +except ImportError: + import queue as Q + class grid: """A two layer routing map. Each cell can be blocked in the vertical @@ -14,11 +20,19 @@ class grid: """ Create a routing map of width x height cells and 2 in the z-axis. """ self.width=width self.height=height + self.source = [] + self.target = [] + self.blocked = [] self.map={} for x in range(width): for y in range(height): for z in range(2): - self.map[x,y,z]=cell() + self.map[vector3d(x,y,z)]=cell() + + # priority queue for the maze routing + self.q = Q.PriorityQueue() + + def view(self,): """ @@ -35,8 +49,8 @@ class grid: cell.scale = 1.5 * (self.width+self.height) for x in range(self.width): for y in range(self.height): - h_map[x,y] = self.map[x,y,0].get_color() - v_map[x,y] = self.map[x,y,1].get_color() + h_map[x,y] = self.map[vector3d(x,y,0)].get_color() + v_map[x,y] = self.map[vector3d(x,y,1)].get_color() v_img = Image.fromarray(v_map, 'RGB').rotate(90) mid_img = Image.fromarray(mid_map, 'RGB').rotate(90) @@ -52,7 +66,8 @@ class grid: def set_property(self,ll,ur,z,name,value=True): for x in range(int(ll[0]),int(ur[0])): for y in range(int(ll[1]),int(ur[1])): - setattr (self.map[x,y,z], name, True) + setattr (self.map[vector3d(x,y,z)], name, True) + getattr (self, name).append(vector3d(x,y,z)) def add_blockage(self,ll,ur,z): debug.info(1,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) @@ -60,9 +75,114 @@ class grid: def set_source(self,ll,ur,z): debug.info(1,"Adding source ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) - self.set_property(ll,ur,z,"is_source") + self.set_property(ll,ur,z,"source") + def set_target(self,ll,ur,z): debug.info(1,"Adding target ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) - self.set_property(ll,ur,z,"is_target") + self.set_property(ll,ur,z,"target") + + def set_path(self,path): + """ + Mark the path in the routing grid for visualization + """ + for p in path: + self.map[p].path=True + def route(self): + """ + This does the A* maze routing. + """ + + # Make sure the queue is empty if we run another route + while not self.q.empty(): + self.q.get() + + # Put the source items into the queue + self.init_queue() + cheapest_path = None + cheapest_cost = None + + # Keep expanding and adding to the priority queue until we are done + while not self.q.empty(): + (cost,path) = self.q.get() + debug.info(2,"Expanding: cost=" + str(cost) + " " + str(path)) + + # expand the last element + neighbors = self.expand_dirs(path[-1]) + debug.info(2,"Neighbors: " + str(neighbors)) + + for n in neighbors: + newpath = path + [n] + # check if we hit the target and are done + if self.is_target(n): + return newpath + else: + # path cost + predicted cost + cost = len(newpath) + self.cost_to_target(n) + self.q.put((cost,newpath)) + + debug.error("Unable to route path. Expand area?",-1) + + def is_target(self,point): + """ + Point is in the target set, so we are done. + """ + return point in self.target + + def expand_dirs(self,point): + """ + Expand each of the four cardinal directions plus up or down + but not expanding to blocked cells. Always follow horizontal/vertical + routing layer requirements. Extend in the future if not routable? + """ + neighbors = [] + # check z layer for enforced direction routing + if point.z==0: + east = point + vector3d(1,0,0) + west= point + vector3d(-11,0,0) + if east.x=0 and not self.map[west].blocked: + neighbors.append(west) + up = point + vector3d(0,0,1) + if not self.map[up].blocked: + neighbors.append(up) + elif point.z==1: + north = point + vector3d(0,1,0) + south = point + vector3d(0,-1,0) + if north.y=0 and not self.map[south].blocked: + neighbors.append(south) + + down = point + vector3d(0,0,-1) + if not self.map[down].blocked: + neighbors.append(down) + + + + return neighbors + + + def init_queue(self): + """ + Populate the queue with all the source pins with cost + to the target. Each item is a path of the grid cells. + We will use an A* search, so this cost must be pessimistic. + Cost so far will be the length of the path. + """ + debug.info(0,"Initializing queue.") + for s in self.source: + cost = self.cost_to_target(s) + debug.info(1,"Init: cost=" + str(cost) + " " + str([s])) + self.q.put((cost,[s])) + + def cost_to_target(self,source): + """ + Find the cheapest HPWL distance to any target point + """ + cost = source.hpwl(self.target[0]) + for t in self.target: + cost = min(source.hpwl(t),cost) + return cost diff --git a/compiler/router/router.py b/compiler/router/router.py index 6b4eef20..2f56772b 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -6,6 +6,8 @@ import debug from vector import vector import grid + + class router: """A router class to read an obstruction map from a gds and plan a @@ -51,15 +53,10 @@ class router: """ If we want to route something besides the top-level cell.""" self.top_name = top_name - def set_layers(self, layers): """ Allows us to change the layers that we are routing on. """ self.layers = layers (horiz_layer, via_layer, vert_layer) = self.layers - if (via_layer != None): - self.via_layer_name = via_layer - else: - self.via_layer_name = None self.vert_layer_name = vert_layer self.vert_layer_width = tech.drc["minwidth_{0}".format(vert_layer)] @@ -111,7 +108,11 @@ class router: for layer in self.layers: self.write_obstacle(self.top_name) - + + def route(self): + path = self.rg.route() + debug.info(0,"Found path: " + str(path)) + self.rg.set_path(path) def add_route(self,start, end, layerstack): """ Add a wire route from the start to the end point""" @@ -136,7 +137,7 @@ class router: return coordinate def min_max_coord(self, coordTrans): - """Find the lowest and highest conner of a Rectangle""" + """Find the lowest and highest corner of a Rectangle""" coordinate = [] minx = min(coordTrans[0][0], coordTrans[1][0], coordTrans[2][0], coordTrans[3][0]) maxx = max(coordTrans[0][0], coordTrans[1][0], coordTrans[2][0], coordTrans[3][0]) @@ -152,6 +153,7 @@ class router: debug.info(0,"Set source: " + str(name) + " " + str(shape) + " z=" + str(zindex)) self.rg.set_source(shape[0],shape[1],zindex) + def set_target(self,name): shape = self.find_pin(name) zindex = 0 if self.pin_layers[name]==self.horiz_layer_number else 1 @@ -171,6 +173,7 @@ class router: shape_tracks=self.convert_to_tracks([ll_microns,ur_microns]) + # don't add a blockage if this shape was a pin shape if shape_tracks not in self.pin_shapes.values(): # inflate the ll and ur by 1 track in each direction [ll,ur]=shape_tracks @@ -183,7 +186,6 @@ class router: debug.info(2,"Skip: "+str(shape_tracks)) - # recurse given the mirror, angle, etc. for cur_sref in self.layout.structures[sref].srefs: sMirr = 1 @@ -201,22 +203,19 @@ class router: self.write_obstacle(cur_sref.sName, layer,sMirr, sAngle, sxyShift) - def inflate_obstacle(self,shape): - # TODO: inflate by the layer design rules - return shape - def convert_to_tracks(self,shape): """ Convert a rectangular shape into track units. """ [ll,ur] = shape - # fix offset + # offset lowest corner object to to (0,0) ll = snap_to_grid(ll-self.offset) ur = snap_to_grid(ur-self.offset) # always round down, because we will add a track - # to inflate each object later + # to inflate each obstacle object later. + # whereas pins should be conservative ll = ll.scale(self.track_factor).ceil() ur = ur.scale(self.track_factor).floor() diff --git a/compiler/router/tests/01_no_blockages_test.py b/compiler/router/tests/01_no_blockages_test.py index 7aa86779..17d79660 100644 --- a/compiler/router/tests/01_no_blockages_test.py +++ b/compiler/router/tests/01_no_blockages_test.py @@ -23,12 +23,10 @@ class no_blockages_test(unittest.TestCase): r.set_layers(("metal1","via1","metal2")) r.create_routing_grid() - r.set_source("A") - r.set_target("B") - r.find_blockages() + r.route() r.rg.view() #drc_errors = calibre.run_drc(name, gds_name) diff --git a/compiler/router/vector3d.py b/compiler/router/vector3d.py new file mode 100644 index 00000000..655e8ff3 --- /dev/null +++ b/compiler/router/vector3d.py @@ -0,0 +1,145 @@ +import debug +import math + +class vector3d(): + """ + This is the vector3d class to represent a 3D coordinate. + It needs to override several operators to support + concise vector3d operations, output, and other more complex + data structures like lists. + """ + def __init__(self, x, y=None, z=None): + """ init function support two init method""" + # will take single input as a coordinate + if y==None: + self.x = x[0] + self.y = x[1] + self.z = x[2] + #will take two inputs as the values of a coordinate + else: + self.x = x + self.y = y + self.z = z + self.tpl=(x,y,z) + + def __str__(self): + """ override print function output """ + return "vector3d:["+str(self.x)+", "+str(self.y)+", "+str(self.z)+"]" + + def __repr__(self): + """ override print function output """ + return "["+str(self.x)+", "+str(self.y)+", "+str(self.z)+"]" + + def __setitem__(self, index, value): + """ + override setitem function + can set value by vector3d[index]=value + """ + if index==0: + self.x=value + elif index==1: + self.y=value + elif index==2: + self.z=value + else: + self.x=value[0] + self.y=value[1] + self.z=value[2] + + def __getitem__(self, index): + """ + override getitem function + can get value by value=vector3d[index] + """ + if index==0: + return self.x + elif index==1: + return self.y + elif index==2: + return self.z + else: + return self + + def __add__(self, other): + """ + Override + function (left add) + Can add by vector3d(x1,y1,z1)+vector(x2,y2,z2) + """ + return vector3d(self.x + other[0], self.y + other[1], self.z + other[2]) + + + def __radd__(self, other): + """ + Override + function (right add) + """ + if other == 0: + return self + else: + return self.__add__(other) + + def __sub__(self, other): + """ + Override - function (left) + """ + return vector3d(self.x - other[0], self.y - other[1], self.z - other[2]) + + def __hash__(self): + """ + Override - function (hash) + Note: This assumes that you DON'T CHANGE THE VECTOR or it will + break things. + """ + return hash(self.tpl) + + + def __rsub__(self, other): + """ + Override - function (right) + """ + return vector3d(other[0]- self.x, other[1] - self.y, other[2] - self.z) + + def rotate(self): + """ pass a copy of rotated vector3d, without altering the vector3d! """ + return vector3d(self.y,self.x,self.z) + + def scale(self, x_factor, y_factor=None,z_factor=None): + """ pass a copy of scaled vector3d, without altering the vector3d! """ + if y_factor==None: + z_factor=x_factor[2] + y_factor=x_factor[1] + x_factor=x_factor[0] + return vector3d(self.x*x_factor,self.y*y_factor,self.z*z_factor) + + def rotate_scale(self, x_factor, y_factor=None, z_factor=None): + """ pass a copy of scaled vector3d, without altering the vector3d! """ + if y_factor==None: + z_factor=x_factor[2] + y_factor=x_factor[1] + x_factor=x_factor[0] + return vector3d(self.y*x_factor,self.x*y_factor,self.z*z_factor) + + def __eq__(self, other): + """Override the default Equals behavior""" + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + return False + + def __ne__(self, other): + """Override the default non-equality behavior""" + return not self.__eq__(other) + + def max(self, other): + """ Max of both values """ + return vector3d(max(self.x,other.x),max(self.y,other.y),max(self.z,other.z)) + + def min(self, other): + """ Min of both values """ + return vector3d(min(self.x,other.x),min(self.y,other.y),min(self.z,other.z)) + + def hpwl(self, other): + """ Return half perimeter wire length from point to another. + Either point can have positive or negative coordinates. """ + hpwl = max(abs(self.x-other.x),abs(other.x-self.x)) + hpwl += max(abs(self.y-other.y),abs(other.y-self.y)) + hpwl += max(abs(self.z-other.z),abs(other.z-self.z)) + return hpwl