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, A single cell that can be occupied in a given layer, blocked,
visited, etc. visited, etc.
""" """
scale=1
def __init__(self): def __init__(self):
self.visited = 0 self.path = False
self.blocked = False self.blocked = False
self.is_source = False self.source = False
self.is_target = False self.target = False
def get_color(self): def get_color(self):
# Blues are horizontal # Blues are horizontal
if self.blocked: if self.blocked:
return ImageColor.getrgb("Blue") return ImageColor.getrgb("Green")
# Reds are source/sink # Reds are source/sink
if self.is_source or self.is_target: if self.source or self.target:
return ImageColor.getrgb("Red") return ImageColor.getrgb("Red")
if self.visited>0: if self.path:
return [255-min(int(self.visited/cell.scale * 255),255)] * 3 return ImageColor.getrgb("Blue")
return [255,255,255] return [255,255,255]

View File

@ -1,8 +1,14 @@
import numpy as np import numpy as np
from PIL import Image from PIL import Image
import debug import debug
from vector3d import vector3d
from cell import cell from cell import cell
try:
import Queue as Q # ver. < 3.0
except ImportError:
import queue as Q
class grid: class grid:
"""A two layer routing map. Each cell can be blocked in the vertical """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. """ """ Create a routing map of width x height cells and 2 in the z-axis. """
self.width=width self.width=width
self.height=height self.height=height
self.source = []
self.target = []
self.blocked = []
self.map={} self.map={}
for x in range(width): for x in range(width):
for y in range(height): for y in range(height):
for z in range(2): 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,): def view(self,):
""" """
@ -35,8 +49,8 @@ class grid:
cell.scale = 1.5 * (self.width+self.height) cell.scale = 1.5 * (self.width+self.height)
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[x,y,0].get_color() h_map[x,y] = self.map[vector3d(x,y,0)].get_color()
v_map[x,y] = self.map[x,y,1].get_color() v_map[x,y] = self.map[vector3d(x,y,1)].get_color()
v_img = Image.fromarray(v_map, 'RGB').rotate(90) v_img = Image.fromarray(v_map, 'RGB').rotate(90)
mid_img = Image.fromarray(mid_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): def set_property(self,ll,ur,z,name,value=True):
for x in range(int(ll[0]),int(ur[0])): for x in range(int(ll[0]),int(ur[0])):
for y in range(int(ll[1]),int(ur[1])): 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): def add_blockage(self,ll,ur,z):
debug.info(1,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(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): def set_source(self,ll,ur,z):
debug.info(1,"Adding source ll={0} ur={1} z={2}".format(str(ll),str(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): def set_target(self,ll,ur,z):
debug.info(1,"Adding target ll={0} ur={1} z={2}".format(str(ll),str(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 from vector import vector
import grid import grid
class router: class router:
"""A router class to read an obstruction map from a gds and plan a """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.""" """ If we want to route something besides the top-level cell."""
self.top_name = top_name self.top_name = top_name
def set_layers(self, layers): def set_layers(self, layers):
""" Allows us to change the layers that we are routing on. """ """ Allows us to change the layers that we are routing on. """
self.layers = layers self.layers = layers
(horiz_layer, via_layer, vert_layer) = self.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_name = vert_layer
self.vert_layer_width = tech.drc["minwidth_{0}".format(vert_layer)] self.vert_layer_width = tech.drc["minwidth_{0}".format(vert_layer)]
@ -111,7 +108,11 @@ class router:
for layer in self.layers: for layer in self.layers:
self.write_obstacle(self.top_name) 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): def add_route(self,start, end, layerstack):
""" Add a wire route from the start to the end point""" """ Add a wire route from the start to the end point"""
@ -136,7 +137,7 @@ class router:
return coordinate return coordinate
def min_max_coord(self, coordTrans): 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 = [] coordinate = []
minx = min(coordTrans[0][0], coordTrans[1][0], coordTrans[2][0], coordTrans[3][0]) 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]) 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)) debug.info(0,"Set source: " + str(name) + " " + str(shape) + " z=" + str(zindex))
self.rg.set_source(shape[0],shape[1],zindex) self.rg.set_source(shape[0],shape[1],zindex)
def set_target(self,name): def set_target(self,name):
shape = self.find_pin(name) shape = self.find_pin(name)
zindex = 0 if self.pin_layers[name]==self.horiz_layer_number else 1 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]) 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(): if shape_tracks not in self.pin_shapes.values():
# inflate the ll and ur by 1 track in each direction # inflate the ll and ur by 1 track in each direction
[ll,ur]=shape_tracks [ll,ur]=shape_tracks
@ -183,7 +186,6 @@ class router:
debug.info(2,"Skip: "+str(shape_tracks)) debug.info(2,"Skip: "+str(shape_tracks))
# recurse given the mirror, angle, etc. # recurse given the mirror, angle, etc.
for cur_sref in self.layout.structures[sref].srefs: for cur_sref in self.layout.structures[sref].srefs:
sMirr = 1 sMirr = 1
@ -201,22 +203,19 @@ class router:
self.write_obstacle(cur_sref.sName, layer,sMirr, sAngle, sxyShift) 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): def convert_to_tracks(self,shape):
""" """
Convert a rectangular shape into track units. Convert a rectangular shape into track units.
""" """
[ll,ur] = shape [ll,ur] = shape
# fix offset # offset lowest corner object to to (0,0)
ll = snap_to_grid(ll-self.offset) ll = snap_to_grid(ll-self.offset)
ur = snap_to_grid(ur-self.offset) ur = snap_to_grid(ur-self.offset)
# always round down, because we will add a track # 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() ll = ll.scale(self.track_factor).ceil()
ur = ur.scale(self.track_factor).floor() 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.set_layers(("metal1","via1","metal2"))
r.create_routing_grid() r.create_routing_grid()
r.set_source("A") r.set_source("A")
r.set_target("B") r.set_target("B")
r.find_blockages() r.find_blockages()
r.route()
r.rg.view() r.rg.view()
#drc_errors = calibre.run_drc(name, gds_name) #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