diff --git a/compiler/router/grid.py b/compiler/router/grid.py index 5f1f3557..ad7636e5 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -1,5 +1,6 @@ import numpy as np from PIL import Image +from itertools import tee import debug from vector3d import vector3d @@ -24,50 +25,52 @@ class grid: self.target = [] self.blocked = [] self.map={} - for x in range(width): - for y in range(height): - for z in range(2): - self.map[vector3d(x,y,z)]=cell() + + # let's leave this sparse, create cells on demand + # for x in range(width): + # for y in range(height): + # for z in range(2): + # self.map[vector3d(x,y,z)]=cell() # priority queue for the maze routing self.q = Q.PriorityQueue() - def view(self,filename="test.png"): - """ - View the data by creating an RGB array and mapping the data - structure to the RGB color palette. - """ + # def view(self,filename="test.png"): + # """ + # View the data by creating an RGB array and mapping the data + # structure to the RGB color palette. + # """ - v_map = np.zeros((self.width,self.height,3), 'uint8') - mid_map = np.ones((10,self.height,3), 'uint8') - h_map = np.ones((self.width,self.height,3), 'uint8') + # v_map = np.zeros((self.width,self.height,3), 'uint8') + # mid_map = np.ones((10,self.height,3), 'uint8') + # h_map = np.ones((self.width,self.height,3), 'uint8') - # We shouldn't have a path greater than 50% the HPWL - # so scale all visited indices by this value for colorization - for x in range(self.width): - for y in range(self.height): - h_map[x,y] = self.map[vector3d(x,y,0)].get_color() - v_map[x,y] = self.map[vector3d(x,y,1)].get_color() - # This is just for scale - if x==0 and y==0: - h_map[x,y] = [0,0,0] - v_map[x,y] = [0,0,0] + # # We shouldn't have a path greater than 50% the HPWL + # # so scale all visited indices by this value for colorization + # for x in range(self.width): + # for y in range(self.height): + # h_map[x,y] = self.map[vector3d(x,y,0)].get_color() + # v_map[x,y] = self.map[vector3d(x,y,1)].get_color() + # # This is just for scale + # if x==0 and y==0: + # h_map[x,y] = [0,0,0] + # v_map[x,y] = [0,0,0] - v_img = Image.fromarray(v_map, 'RGB').rotate(90) - #v_img.show() - mid_img = Image.fromarray(mid_map, 'RGB').rotate(90) - h_img = Image.fromarray(h_map, 'RGB').rotate(90) - #h_img.show() + # v_img = Image.fromarray(v_map, 'RGB').rotate(90) + # #v_img.show() + # mid_img = Image.fromarray(mid_map, 'RGB').rotate(90) + # h_img = Image.fromarray(h_map, 'RGB').rotate(90) + # #h_img.show() - # concatenate them into a plot with the two layers - img = Image.new('RGB', (2*self.width+10, self.height)) - img.paste(h_img, (0,0)) - img.paste(mid_img, (self.width,0)) - img.paste(v_img, (self.width+10,0)) - #img.show() - img.save(filename) + # # concatenate them into a plot with the two layers + # img = Image.new('RGB', (2*self.width+10, self.height)) + # img.paste(h_img, (0,0)) + # img.paste(mid_img, (self.width,0)) + # img.paste(v_img, (self.width+10,0)) + # #img.show() + # img.save(filename) def set_property(self,ll,ur,z,name,value=True): assert(ur[1] >= ll[1] and ur[0] >= ll[0]) @@ -77,8 +80,11 @@ class grid: assert(ur[1]=0) for x in range(int(ll[0]),int(ur[0])+1): for y in range(int(ll[1]),int(ur[1])+1): - setattr (self.map[vector3d(x,y,z)], name, True) - getattr (self, name).append(vector3d(x,y,z)) + n = vector3d(x,y,z) + self.add_map(n) + setattr (self.map[n], name, True) + if n not in getattr(self, name): + getattr(self, name).append(n) def add_blockage(self,ll,ur,z): debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) @@ -100,11 +106,16 @@ class grid: for p in path: self.map[p].path=True - def route(self): + def route(self,cost_bound=0): """ - This does the A* maze routing. + This does the A* maze routing with preferred direction routing. """ - + + # We set a cost bound of 2.5 x the HPWL for run-time. This can be + # over-ridden if the route fails due to pruning a feasible solution. + if (cost_bound==0): + cost_bound = 2.5*self.cost_to_target(self.source[0]) + # Make sure the queue is empty if we run another route while not self.q.empty(): self.q.get() @@ -117,7 +128,7 @@ class grid: # Keep expanding and adding to the priority queue until we are done while not self.q.empty(): (cost,path) = self.q.get() - debug.info(4,"Expanding: cost=" + str(cost) + " " + str(path)) + debug.info(2,"Expanding: cost=" + str(cost) + " " + str(path)) # expand the last element neighbors = self.expand_dirs(path) @@ -125,15 +136,19 @@ class grid: for n in neighbors: newpath = path + [n] + if n not in self.map.keys(): + self.map[n]=cell() self.map[n].visited=True # check if we hit the target and are done if self.is_target(n): - return newpath + return (newpath,self.cost(newpath)) else: - # path cost + predicted cost - cost = self.cost(newpath) + self.cost_to_target(n) - self.q.put((cost,newpath)) + # potential path cost + predicted cost + cost = self.cost(newpath) + self.cost_to_target(n) + # only add the cost if it is less than our bound + if (cost < cost_bound): + self.q.put((cost,newpath)) debug.error("Unable to route path. Expand area?",-1) @@ -146,42 +161,54 @@ class grid: def expand_dirs(self,path): """ 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? - Future: Do we want to allow non-preferred direction routing? + but not expanding to blocked cells. Expands in all directions + regardless of preferred directions. """ # expand from the last point point = path[-1] neighbors = [] - # check z layer for enforced direction routing - if point.z==0: - east = point + vector3d(1,0,0) - west= point + vector3d(-1,0,0) - if east.x=0 and not self.map[west].blocked and not west in path: - neighbors.append(west) + + east = point + vector3d(1,0,0) + self.add_map(east) + if not self.map[east].blocked and not east in path: + neighbors.append(east) + + west= point + vector3d(-1,0,0) + self.add_map(west) + if not self.map[west].blocked and not west in path: + neighbors.append(west) - up = point + vector3d(0,0,1) - if not self.map[up].blocked and not up in path: + up = point + vector3d(0,0,1) + self.add_map(up) + if up.z<2 and not self.map[up].blocked and not up in path: 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 and not south in path: - neighbors.append(south) + + north = point + vector3d(0,1,0) + self.add_map(north) + if not self.map[north].blocked and not north in path: + neighbors.append(north) + + south = point + vector3d(0,-1,0) + self.add_map(south) + if not self.map[south].blocked and not south in path: + neighbors.append(south) - down = point + vector3d(0,0,-1) - if not self.map[down].blocked and not down in path: - neighbors.append(down) + down = point + vector3d(0,0,-1) + self.add_map(down) + if down.z>=0 and not self.map[down].blocked and not down in path: + neighbors.append(down) return neighbors - + + def add_map(self,p): + """ + Add a point to the map if it doesn't exist. + """ + if p not in self.map.keys(): + self.map[p]=cell() def init_queue(self): """ @@ -190,7 +217,7 @@ class grid: We will use an A* search, so this cost must be pessimistic. Cost so far will be the length of the path. """ - debug.info(1,"Initializing queue.") + debug.info(4,"Initializing queue.") for s in self.source: cost = self.cost_to_target(s) debug.info(4,"Init: cost=" + str(cost) + " " + str([s])) @@ -205,18 +232,52 @@ class grid: cost = min(source.hpwl(t),cost) return cost + + + def cost(self,path): """ The cost of the path is the length plus a penalty for the number of vias. - Future: Do we want to allow non-preferred direction routing? + We assume that non-preferred direction is penalized 2x. """ - prev_layer = path[0].z - via_cost = 0 - for p in path: - if p.z != prev_layer: - via_cost += 2 # we count a via as 2x a wire track - prev_layer = p.z - - return len(path)+via_cost + # Ignore the source pin layer change, FIXME? + def pairwise(iterable): + "s -> (s0,s1), (s1,s2), (s2, s3), ..." + a, b = tee(iterable) + next(b, None) + return zip(a, b) + + plist = pairwise(path) + cost = 0 + for p0,p1 in plist: + if p0.z != p1.z: # via + cost += 2 + elif p0.x != p1.x: # horizontal + cost += 2 if (p0.z == 1) else 1 + elif p0.y != p1.y: # vertical + cost += 2 if (p0.z == 0) else 1 + else: + debug.error("Non-changing direction!") + + # for p in path: + # if p.z != prev_p.z: + # via_cost += 2 # we count a via as 2x a wire track + # prev_layer = p.z + # prev_p = p + # + #return len(path)+via_cost + return cost + def get_inertia(self,p0,p1): + """ + Sets the direction based on the previous direction we came from. + """ + # direction (index) of movement + if p0.x==p1.x: + return 1 + elif p0.y==p1.y: + return 0 + else: + # z direction + return 2 diff --git a/compiler/router/router.py b/compiler/router/router.py index 3fc701da..b3bf1209 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -154,34 +154,35 @@ class router: self.find_blockages() - self.rg.view("preroute.png") + #self.rg.view("preroute.png") # returns the path in tracks - self.path = self.rg.route() - debug.info(1,"Found path. ") + (self.path,cost) = self.rg.route() + debug.info(1,"Found path: cost={0} ".format(cost)) debug.info(2,str(self.path)) self.set_path(self.path) - self.rg.view("postroute.png") + #self.rg.view("postroute.png") return def add_route(self,cell): """ Add the current wire route to the given design instance. """ - # First, simplify the path for + # First, simplify the path for + debug.info(1,str(self.path)) contracted_path = self.contract_path(self.path) debug.info(1,str(contracted_path)) # Make sure there's a pin enclosure on the source and dest src_shape = self.convert_track_to_shape(contracted_path[0]) - cell.add_rect(layer=self.layers[0], + cell.add_rect(layer=self.layers[contracted_path[0].z], offset=src_shape[0], width=src_shape[1].x-src_shape[0].x, height=src_shape[1].y-src_shape[0].y) dest_shape = self.convert_track_to_shape(contracted_path[-1]) - cell.add_rect(layer=self.layers[0], + cell.add_rect(layer=self.layers[contracted_path[-1].z], offset=dest_shape[0], width=dest_shape[1].x-dest_shape[0].x, height=dest_shape[1].y-dest_shape[0].y) @@ -189,8 +190,22 @@ class router: # convert the path back to absolute units from tracks abs_path = map(self.convert_point_to_units,contracted_path) - debug.info(1,str(abs_path)) cell.add_wire(self.layers,abs_path) + debug.info(1,str(abs_path)) + + # Check if a via is needed at the start point + if (contracted_path[0].z!=contracted_path[1].z): + # offset this by 1/2 the via size + c=contact(self.layers, (1, 1)) + via_offset = vector(-0.5*c.width,-0.5*c.height) + cell.add_via(self.layers,abs_path[0]+via_offset) + # Check if a via is needed at the end point + if (contracted_path[-1].z!=contracted_path[-2].z): + # offset this by 1/2 the via size + c=contact(self.layers, (1, 1)) + via_offset = vector(-0.5*c.width,-0.5*c.height) + cell.add_via(self.layers,abs_path[-1]+via_offset) + def create_steiner_routes(self,pins): @@ -245,10 +260,10 @@ class router: Sets the direction based on the previous direction we came from. """ # direction (index) of movement - if p0.x==p1.x: - return 1 - elif p0.y==p1.y: + if p0.x!=p1.x: return 0 + elif p0.y!=p1.y: + return 1 else: # z direction return 2 diff --git a/compiler/router/tests/01_no_blockages_test.py b/compiler/router/tests/01_no_blockages_test.py index b4524c48..4847aeea 100644 --- a/compiler/router/tests/01_no_blockages_test.py +++ b/compiler/router/tests/01_no_blockages_test.py @@ -51,6 +51,7 @@ class no_blockages_test(unittest.TestCase): layer_stack =("metal1","via1","metal2") r.route(layer_stack,src="A",dest="B") r.add_route(self) + self.gds_write("mytemp.gds") r = routing("test1", "01_no_blockages_test")