Routing multilayer, around blockages.

This commit is contained in:
Matt Guthaus 2016-11-16 16:47:31 -08:00
parent b947989970
commit 784bad2e99
5 changed files with 292 additions and 32 deletions

View File

@ -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]

View File

@ -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<self.width and not self.map[east].blocked:
neighbors.append(east)
if west.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<self.height and not self.map[north].blocked:
neighbors.append(north)
if south.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

View File

@ -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()

View File

@ -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)

145
compiler/router/vector3d.py Normal file
View File

@ -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