mirror of https://github.com/VLSIDA/OpenRAM.git
Routing multilayer, around blockages.
This commit is contained in:
parent
b947989970
commit
784bad2e99
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue