Fixed offgrid pins. Added vias to src/dst pins. Added preferred direction routing costs.

This commit is contained in:
mguthaus 2017-04-14 13:18:35 -07:00
parent 0766db9e11
commit 76f338e982
3 changed files with 166 additions and 89 deletions

View File

@ -1,5 +1,6 @@
import numpy as np import numpy as np
from PIL import Image from PIL import Image
from itertools import tee
import debug import debug
from vector3d import vector3d from vector3d import vector3d
@ -24,50 +25,52 @@ class grid:
self.target = [] self.target = []
self.blocked = [] self.blocked = []
self.map={} self.map={}
for x in range(width):
for y in range(height): # let's leave this sparse, create cells on demand
for z in range(2): # for x in range(width):
self.map[vector3d(x,y,z)]=cell() # for y in range(height):
# for z in range(2):
# self.map[vector3d(x,y,z)]=cell()
# priority queue for the maze routing # priority queue for the maze routing
self.q = Q.PriorityQueue() self.q = Q.PriorityQueue()
def view(self,filename="test.png"): # def view(self,filename="test.png"):
""" # """
View the data by creating an RGB array and mapping the data # View the data by creating an RGB array and mapping the data
structure to the RGB color palette. # structure to the RGB color palette.
""" # """
v_map = np.zeros((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') # mid_map = np.ones((10,self.height,3), 'uint8')
h_map = np.ones((self.width,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 # # We shouldn't have a path greater than 50% the HPWL
# so scale all visited indices by this value for colorization # # so scale all visited indices by this value for colorization
for x in range(self.width): # for x in range(self.width):
for y in range(self.height): # for y in range(self.height):
h_map[x,y] = self.map[vector3d(x,y,0)].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_map[x,y] = self.map[vector3d(x,y,1)].get_color()
# This is just for scale # # This is just for scale
if x==0 and y==0: # if x==0 and y==0:
h_map[x,y] = [0,0,0] # h_map[x,y] = [0,0,0]
v_map[x,y] = [0,0,0] # v_map[x,y] = [0,0,0]
v_img = Image.fromarray(v_map, 'RGB').rotate(90) # v_img = Image.fromarray(v_map, 'RGB').rotate(90)
#v_img.show() # #v_img.show()
mid_img = Image.fromarray(mid_map, 'RGB').rotate(90) # mid_img = Image.fromarray(mid_map, 'RGB').rotate(90)
h_img = Image.fromarray(h_map, 'RGB').rotate(90) # h_img = Image.fromarray(h_map, 'RGB').rotate(90)
#h_img.show() # #h_img.show()
# concatenate them into a plot with the two layers # # concatenate them into a plot with the two layers
img = Image.new('RGB', (2*self.width+10, self.height)) # img = Image.new('RGB', (2*self.width+10, self.height))
img.paste(h_img, (0,0)) # img.paste(h_img, (0,0))
img.paste(mid_img, (self.width,0)) # img.paste(mid_img, (self.width,0))
img.paste(v_img, (self.width+10,0)) # img.paste(v_img, (self.width+10,0))
#img.show() # #img.show()
img.save(filename) # img.save(filename)
def set_property(self,ll,ur,z,name,value=True): def set_property(self,ll,ur,z,name,value=True):
assert(ur[1] >= ll[1] and ur[0] >= ll[0]) assert(ur[1] >= ll[1] and ur[0] >= ll[0])
@ -77,8 +80,11 @@ class grid:
assert(ur[1]<self.height and ur[1]>=0) assert(ur[1]<self.height and ur[1]>=0)
for x in range(int(ll[0]),int(ur[0])+1): for x in range(int(ll[0]),int(ur[0])+1):
for y in range(int(ll[1]),int(ur[1])+1): for y in range(int(ll[1]),int(ur[1])+1):
setattr (self.map[vector3d(x,y,z)], name, True) n = vector3d(x,y,z)
getattr (self, name).append(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): def add_blockage(self,ll,ur,z):
debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(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: for p in path:
self.map[p].path=True 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 # Make sure the queue is empty if we run another route
while not self.q.empty(): while not self.q.empty():
self.q.get() self.q.get()
@ -117,7 +128,7 @@ class grid:
# Keep expanding and adding to the priority queue until we are done # Keep expanding and adding to the priority queue until we are done
while not self.q.empty(): while not self.q.empty():
(cost,path) = self.q.get() (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 # expand the last element
neighbors = self.expand_dirs(path) neighbors = self.expand_dirs(path)
@ -125,15 +136,19 @@ class grid:
for n in neighbors: for n in neighbors:
newpath = path + [n] newpath = path + [n]
if n not in self.map.keys():
self.map[n]=cell()
self.map[n].visited=True self.map[n].visited=True
# check if we hit the target and are done # check if we hit the target and are done
if self.is_target(n): if self.is_target(n):
return newpath return (newpath,self.cost(newpath))
else: else:
# path cost + predicted cost # potential path cost + predicted cost
cost = self.cost(newpath) + self.cost_to_target(n) cost = self.cost(newpath) + self.cost_to_target(n)
self.q.put((cost,newpath)) # 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) debug.error("Unable to route path. Expand area?",-1)
@ -146,42 +161,54 @@ class grid:
def expand_dirs(self,path): def expand_dirs(self,path):
""" """
Expand each of the four cardinal directions plus up or down Expand each of the four cardinal directions plus up or down
but not expanding to blocked cells. Always follow horizontal/vertical but not expanding to blocked cells. Expands in all directions
routing layer requirements. Extend in the future if not routable? regardless of preferred directions.
Future: Do we want to allow non-preferred direction routing?
""" """
# expand from the last point # expand from the last point
point = path[-1] point = path[-1]
neighbors = [] neighbors = []
# check z layer for enforced direction routing
if point.z==0: east = point + vector3d(1,0,0)
east = point + vector3d(1,0,0) self.add_map(east)
west= point + vector3d(-1,0,0) if not self.map[east].blocked and not east in path:
if east.x<self.width and not self.map[east].blocked and not east in path: neighbors.append(east)
neighbors.append(east)
if west.x>=0 and not self.map[west].blocked and not west in path: west= point + vector3d(-1,0,0)
neighbors.append(west) self.add_map(west)
if not self.map[west].blocked and not west in path:
neighbors.append(west)
up = point + vector3d(0,0,1) up = point + vector3d(0,0,1)
if not self.map[up].blocked and not up in path: self.add_map(up)
if up.z<2 and not self.map[up].blocked and not up in path:
neighbors.append(up) neighbors.append(up)
elif point.z==1:
north = point + vector3d(0,1,0) north = point + vector3d(0,1,0)
south = point + vector3d(0,-1,0) self.add_map(north)
if north.y<self.height and not self.map[north].blocked and not north in path: if not self.map[north].blocked and not north in path:
neighbors.append(north) neighbors.append(north)
if south.y>=0 and not self.map[south].blocked and not south in path:
neighbors.append(south) 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) down = point + vector3d(0,0,-1)
if not self.map[down].blocked and not down in path: self.add_map(down)
neighbors.append(down) if down.z>=0 and not self.map[down].blocked and not down in path:
neighbors.append(down)
return neighbors 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): def init_queue(self):
""" """
@ -190,7 +217,7 @@ class grid:
We will use an A* search, so this cost must be pessimistic. We will use an A* search, so this cost must be pessimistic.
Cost so far will be the length of the path. 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: for s in self.source:
cost = self.cost_to_target(s) cost = self.cost_to_target(s)
debug.info(4,"Init: cost=" + str(cost) + " " + str([s])) debug.info(4,"Init: cost=" + str(cost) + " " + str([s]))
@ -205,18 +232,52 @@ class grid:
cost = min(source.hpwl(t),cost) cost = min(source.hpwl(t),cost)
return cost return cost
def cost(self,path): def cost(self,path):
""" """
The cost of the path is the length plus a penalty for the number The cost of the path is the length plus a penalty for the number
of vias. 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 # Ignore the source pin layer change, FIXME?
via_cost = 0 def pairwise(iterable):
for p in path: "s -> (s0,s1), (s1,s2), (s2, s3), ..."
if p.z != prev_layer: a, b = tee(iterable)
via_cost += 2 # we count a via as 2x a wire track next(b, None)
prev_layer = p.z return zip(a, b)
return len(path)+via_cost 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

View File

@ -154,34 +154,35 @@ class router:
self.find_blockages() self.find_blockages()
self.rg.view("preroute.png") #self.rg.view("preroute.png")
# returns the path in tracks # returns the path in tracks
self.path = self.rg.route() (self.path,cost) = self.rg.route()
debug.info(1,"Found path. ") debug.info(1,"Found path: cost={0} ".format(cost))
debug.info(2,str(self.path)) debug.info(2,str(self.path))
self.set_path(self.path) self.set_path(self.path)
self.rg.view("postroute.png") #self.rg.view("postroute.png")
return return
def add_route(self,cell): def add_route(self,cell):
""" """
Add the current wire route to the given design instance. 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) contracted_path = self.contract_path(self.path)
debug.info(1,str(contracted_path)) debug.info(1,str(contracted_path))
# Make sure there's a pin enclosure on the source and dest # Make sure there's a pin enclosure on the source and dest
src_shape = self.convert_track_to_shape(contracted_path[0]) 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], offset=src_shape[0],
width=src_shape[1].x-src_shape[0].x, width=src_shape[1].x-src_shape[0].x,
height=src_shape[1].y-src_shape[0].y) height=src_shape[1].y-src_shape[0].y)
dest_shape = self.convert_track_to_shape(contracted_path[-1]) 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], offset=dest_shape[0],
width=dest_shape[1].x-dest_shape[0].x, width=dest_shape[1].x-dest_shape[0].x,
height=dest_shape[1].y-dest_shape[0].y) height=dest_shape[1].y-dest_shape[0].y)
@ -189,8 +190,22 @@ class router:
# convert the path back to absolute units from tracks # convert the path back to absolute units from tracks
abs_path = map(self.convert_point_to_units,contracted_path) abs_path = map(self.convert_point_to_units,contracted_path)
debug.info(1,str(abs_path))
cell.add_wire(self.layers,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): def create_steiner_routes(self,pins):
@ -245,10 +260,10 @@ class router:
Sets the direction based on the previous direction we came from. Sets the direction based on the previous direction we came from.
""" """
# direction (index) of movement # direction (index) of movement
if p0.x==p1.x: if p0.x!=p1.x:
return 1
elif p0.y==p1.y:
return 0 return 0
elif p0.y!=p1.y:
return 1
else: else:
# z direction # z direction
return 2 return 2

View File

@ -51,6 +51,7 @@ class no_blockages_test(unittest.TestCase):
layer_stack =("metal1","via1","metal2") layer_stack =("metal1","via1","metal2")
r.route(layer_stack,src="A",dest="B") r.route(layer_stack,src="A",dest="B")
r.add_route(self) r.add_route(self)
self.gds_write("mytemp.gds")
r = routing("test1", "01_no_blockages_test") r = routing("test1", "01_no_blockages_test")