Rename unit test files according to test. Modify off-grid pins and blockages. Reorganize router code a bit.

This commit is contained in:
Matthew Guthaus 2017-04-12 10:59:04 -07:00
parent 1f5841b933
commit 0766db9e11
15 changed files with 166 additions and 127 deletions

View File

@ -650,7 +650,7 @@ class VlsiLayout:
cellBoundary[3]=right_top_Y
return cellBoundary
def readPin(self,label_name):
def readPinShape(self,label_name):
"""
Search for a pin label and return the largest enclosing rectangle
on the same layer as the pin label.
@ -663,7 +663,7 @@ class VlsiLayout:
label_layer = Text.drawingLayer
label_coordinate = Text.coordinates
pin_boundaries=self.readAllPinInStructureList(label_coordinate, label_layer)
pin_boundaries=self.readAllPinShapesInStructureList(label_coordinate, label_layer)
# sort the boundaries, return the max area pin boundary
pin_boundaries.sort(cmpBoundaryAreas,reverse=True)
@ -675,7 +675,7 @@ class VlsiLayout:
return [label_name, label_layer, pin_boundary]
def readAllPin(self,label_name):
def readAllPinShapes(self,label_name):
"""
Search for a pin label and return ALL the enclosing rectangles on the same layer
as the pin label.
@ -688,7 +688,7 @@ class VlsiLayout:
label_layer = Text.drawingLayer
label_coordinate = Text.coordinates
pin_boundaries=self.readAllPinInStructureList(label_coordinate, label_layer)
pin_boundaries=self.readAllPinShapesInStructureList(label_coordinate, label_layer)
# Convert to user units
new_boundaries = []
@ -699,7 +699,7 @@ class VlsiLayout:
return [label_name, label_layer, new_boundaries]
def readAllPinInStructureList(self,label_coordinates,layer):
def readAllPinShapesInStructureList(self,label_coordinates,layer):
"""
Given the label coordinate, search for enclosing structures on the given layer.
Return the single biggest area rectangle.

View File

@ -34,14 +34,14 @@ class grid:
def view(self):
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((25,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
@ -62,12 +62,12 @@ class grid:
#h_img.show()
# concatenate them into a plot with the two layers
img = Image.new('RGB', (2*self.width+25, self.height))
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+25,0))
img.show()
img.save("test.png")
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])
@ -135,7 +135,6 @@ class grid:
cost = self.cost(newpath) + self.cost_to_target(n)
self.q.put((cost,newpath))
self.view()
debug.error("Unable to route path. Expand area?",-1)
def is_target(self,point):

View File

@ -11,29 +11,33 @@ import grid
class router:
"""A router class to read an obstruction map from a gds and plan a
route on a given layer. This is limited to two layer routes.
"""
def __init__(self, gds_name):
"""Use the gds file for the blockages with the top module topName and
layers for the layers to route on
"""
# Load the gds file and read in all the shapes
self.gds_name = gds_name
self.layout = gdsMill.VlsiLayout(units=tech.GDS["unit"])
self.reader = gdsMill.Gds2reader(self.layout)
self.reader.loadFromFile(gds_name)
self.top_name = self.layout.rootStructureName
# A list of pin names for source and dest
self.pin_names = []
# The map of pin names to list of all pin shapes for a pin.
self.pin_shapes = {}
# Used to track which shapes should not become blockages
self.all_pin_shapes = []
# The corresponding layers of the above pin shapes
self.pin_layers = {}
# Used to track which shapes should not become blockages. This
# will contain all of both source and dest pin shapes in units not tracks.
self.all_pin_shapes = []
# The boundary will determine the limits to the size of the routing grid
self.boundary = self.layout.measureBoundary(self.top_name)
self.ll = vector(self.boundary[0])
self.ur = vector(self.boundary[1])
self.size = self.ur - self.ll
def set_top(self,top_name):
@ -53,7 +57,7 @@ class router:
self.horiz_layer_width = tech.drc["minwidth_{0}".format(horiz_layer)]
self.horiz_layer_number = tech.layer[horiz_layer]
# contacted track spacing
# Contacted track spacing.
via_connect = contact(self.layers, (1, 1))
max_via_size = max(via_connect.width,via_connect.height)
horiz_layer_spacing = tech.drc[str(self.horiz_layer_name)+"_to_"+str(self.horiz_layer_name)]
@ -61,39 +65,42 @@ class router:
self.horiz_track_width = max_via_size + horiz_layer_spacing
self.vert_track_width = max_via_size + vert_layer_spacing
# This is so we can use a single resolution grid for both layers
# We'll keep horizontal and vertical tracks the same for simplicity.
self.track_width = max(self.horiz_track_width,self.vert_track_width)
debug.info(1,"Track width:"+str(self.track_width))
debug.info(1,"Track width: "+str(self.track_width))
self.track_widths = [self.track_width] * 2
self.track_factor = [1/self.track_width] * 2
debug.info(1,"Track factor: {0}".format(self.track_factor))
def create_routing_grid(self):
""" Create a routing grid that spans given area. Wires cannot exist outside region. """
"""
Create a routing grid that spans given area. Wires cannot exist outside region.
"""
# We will add a halo around the boundary
# of this many tracks
track_halo = 5
debug.info(1,"Size: {0} x {1}".format(self.size.x,self.size.y))
size = self.ur - self.ll
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
# pad the tracks on each side by the halo as well
self.left_in_tracks = int(math.floor(self.ll.x/self.track_width)) - track_halo
self.bottom_in_tracks = int(math.floor(self.ll.y/self.track_width)) - track_halo
self.right_in_tracks = int(math.ceil(self.ur.x/self.track_width)) + track_halo
self.top_in_tracks = int(math.ceil(self.ur.y/self.track_width)) + track_halo
# The routing grid starts at the self.ll and goes up/right
# The +1 is because the source/dest object may get expanded outside the region
self.height_in_tracks = int(math.ceil(self.ur.x/self.track_width))+2
self.width_in_tracks = int(math.ceil(self.ur.y/self.track_width))+2
# We will offset so th lower left is track 0,0
self.track_offset = vector(-self.left_in_tracks,-self.bottom_in_tracks)
self.width_in_tracks = self.right_in_tracks - self.left_in_tracks
self.height_in_tracks = self.top_in_tracks - self.bottom_in_tracks
debug.info(1,"Size (in tracks): {0} x {1}".format(self.width_in_tracks, self.height_in_tracks))
debug.info(1,"Size (in tracks, from ll): {0} x {1}".format(self.width_in_tracks, self.height_in_tracks))
self.rg = grid.grid(self.height_in_tracks,self.width_in_tracks)
def find_pin(self,pin):
""" Finds the pin shapes and converts to tracks """
(pin_name,pin_layer,pin_shapes) = self.layout.readAllPin(str(pin))
"""
Finds the pin shapes and converts to tracks
"""
# Returns all the shapes that enclose a pin on a given layer
(pin_name,pin_layer,pin_shapes) = self.layout.readAllPinShapes(str(pin))
self.pin_shapes[str(pin)]=[]
self.pin_names.append(pin_name)
@ -102,21 +109,26 @@ class router:
debug.info(2,"Find pin {0} layer {1} shape {2}".format(pin_name,str(pin_layer),str(pin_shape)))
# repack the shape as a pair of vectors rather than four values
shape=[vector(pin_shape[0],pin_shape[1]),vector(pin_shape[2],pin_shape[3])]
new_shape = self.convert_shape_to_tracks(shape,round_bigger=False)
self.pin_shapes[str(pin)].append(new_shape)
self.all_pin_shapes.append(new_shape)
# convert the pin coordinates to tracks and round the sizes down
self.pin_shapes[str(pin)].append(shape)
self.pin_layers[str(pin)] = pin_layer
self.all_pin_shapes.append(shape)
return self.pin_shapes[str(pin)]
def find_blockages(self):
"""
Iterate through all the layers and write the obstacles to the routing grid.
"""
if len(self.pin_names)!=2:
debug.error("Must set pins before creating blockages.",-1)
for layer in self.layers:
self.write_obstacle(self.top_name)
def clear_pins(self):
"""
Reset the source and destination pins to start a new routing.
"""
self.source = []
self.dest = []
@ -125,42 +137,79 @@ class router:
Route a single source-destination net and return
the simplified rectilinear path.
"""
# Clear the pins if we have previously routed
self.clear_pins()
# Set up layers and track sizes
self.set_layers(layers)
# Creat a routing grid over the entire area
# FIXME: This could be created only over the routing region,
# but this is simplest for now.
self.create_routing_grid()
self.set_source(src)
self.set_target(dest)
self.find_blockages()
self.rg.view("preroute.png")
# returns the path in tracks
path = self.rg.route()
self.path = self.rg.route()
debug.info(1,"Found path. ")
debug.info(2,str(path))
self.set_path(path)
# First, simplify the path.
contracted_path = self.contract_path(path)
debug.info(1,str(contracted_path))
debug.info(2,str(self.path))
self.set_path(self.path)
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
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],
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],
offset=dest_shape[0],
width=dest_shape[1].x-dest_shape[0].x,
height=dest_shape[1].y-dest_shape[0].y)
# 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))
# Make sure there's a pin enclosure on the source and dest
src_shape = self.convert_track_to_shape(contracted_path[0])
dest_shape = self.convert_track_to_shape(contracted_path[-1])
cell.add_wire(self.layers,abs_path)
return (src_shape,abs_path,dest_shape)
def create_steiner_routes(self,pins):
"""Find a set of steiner points and then return the list of
point-to-point routes."""
"""
Find a set of steiner points and then return the list of
point-to-point routes.
"""
pass
def find_steiner_points(self,pins):
""" Find the set of steiner points and return them."""
"""
Find the set of steiner points and return them.
"""
pass
def translate_coordinates(self, coord, mirr, angle, xyShift):
"""Calculate coordinates after flip, rotate, and shift"""
"""
Calculate coordinates after flip, rotate, and shift
"""
coordinate = []
for item in coord:
x = (item[0]*math.cos(angle)-item[1]*mirr*math.sin(angle)+xyShift[0])
@ -169,7 +218,9 @@ class router:
return coordinate
def convert_shape_to_units(self, shape):
""" Scale a shape (two vector list) to user units """
"""
Scale a shape (two vector list) to user units
"""
unit_factor = [tech.GDS["unit"][0]] * 2
ll=shape[0].scale(unit_factor)
ur=shape[1].scale(unit_factor)
@ -177,7 +228,9 @@ class router:
def min_max_coord(self, coord):
"""Find the lowest and highest corner of a Rectangle"""
"""
Find the lowest and highest corner of a Rectangle
"""
coordinate = []
minx = min(coord[0][0], coord[1][0], coord[2][0], coord[3][0])
maxx = max(coord[0][0], coord[1][0], coord[2][0], coord[3][0])
@ -188,71 +241,81 @@ class router:
return coordinate
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:
inertia = 1
return 1
elif p0.y==p1.y:
inertia = 0
return 0
else:
inertia = 2
return inertia
# z direction
return 2
def contract_path(self,path):
"""
Remove intermediate points in a rectilinear path.
Remove intermediate points in a rectilinear path.
"""
newpath = [path[0]]
for i in range(len(path)-1):
if i==0:
continue
for i in range(1,len(path)-1):
prev_inertia=self.get_inertia(path[i-1],path[i])
next_inertia=self.get_inertia(path[i],path[i+1])
# if we switch directions, add the point, otherwise don't
if prev_inertia!=next_inertia:
newpath.append(path[i])
else:
continue
# always add the last path
newpath.append(path[-1])
return newpath
def set_path(self,path):
"""
Mark the path in the routing grid.
"""
debug.info(3,"Set path: " + str(path))
self.rg.set_path(path)
def set_source(self,name):
"""
Mark the grids that are in the pin rectangle ranges to have the source property.
"""
shapes = self.find_pin(name)
zindex = 0 if self.pin_layers[name]==self.horiz_layer_number else 1
for shape in shapes:
debug.info(1,"Set source: " + str(name) + " " + str(shape) + " z=" + str(zindex))
self.rg.set_source(shape[0],shape[1],zindex)
shape_in_tracks=self.convert_shape_to_tracks(shape)
debug.info(1,"Set source: " + str(name) + " " + str(shape_in_tracks) + " z=" + str(zindex))
self.rg.set_source(shape_in_tracks[0],shape_in_tracks[1],zindex)
def set_target(self,name):
"""
Mark the grids that are in the pin rectangle ranges to have the target property.
"""
shapes = self.find_pin(name)
zindex = 0 if self.pin_layers[name]==self.horiz_layer_number else 1
for shape in shapes:
debug.info(1,"Set target: " + str(name) + " " + str(shape) + " z=" + str(zindex))
self.rg.set_target(shape[0],shape[1],zindex)
shape_in_tracks=self.convert_shape_to_tracks(shape)
debug.info(1,"Set target: " + str(name) + " " + str(shape_in_tracks) + " z=" + str(zindex))
self.rg.set_target(shape_in_tracks[0],shape_in_tracks[1],zindex)
def write_obstacle(self, sref, mirr = 1, angle = math.radians(float(0)), xyShift = (0, 0)):
"""Recursive write boundaries on each Structure in GDS file to LEF"""
"""
Recursive write boundaries as blockages to the routing grid.
Recurses for each Structure in GDS.
"""
for boundary in self.layout.structures[sref].boundaries:
coord_trans = self.translate_coordinates(boundary.coordinates, mirr, angle, xyShift)
shape_coords = self.min_max_coord(coord_trans)
shape = self.convert_shape_to_units(shape_coords)
# only consider the two layers that we are routing on
if boundary.drawingLayer in [self.vert_layer_number,self.horiz_layer_number]:
# We round the pins down, so we must do this to skip them
pin_shape_tracks=self.convert_units_to_tracks(shape,round_bigger=False)
zlayer = 0 if boundary.drawingLayer==self.horiz_layer_number else 1
# don't add a blockage if this shape was a pin shape
if pin_shape_tracks not in self.all_pin_shapes:
# inflate the ll and ur by 1 track in each direction
[ll,ur]=self.convert_units_to_tracks(shape)
zlayer = 0 if boundary.drawingLayer==self.horiz_layer_number else 1
if shape not in self.all_pin_shapes:
[ll,ur]=self.convert_shape_to_tracks(shape)
self.rg.add_blockage(ll,ur,zlayer)
@ -277,55 +340,42 @@ class router:
"""
Convert a path set of tracks to center line path.
"""
track_factor = [self.track_width] * 2
# we can ignore the layers here
# add_wire will filter out duplicates
pt = vector(p[0],p[1])
pt=pt.scale(track_factor)
return snap_to_grid(pt)
pt=pt.scale(self.track_widths)
return pt
def convert_units_to_tracks(self,shape,round_bigger=True):
def convert_shape_to_tracks(self,shape,round_bigger=False):
"""
Convert a rectangular shape into track units.
"""
[ll,ur] = shape
# offset lowest corner object to to (-track halo,-track halo)
ll = snap_to_grid(ll)
ur = snap_to_grid(ur)
# to scale coordinates to tracks
track_factor = [1/self.track_width] * 2
if round_bigger: # Always round blockage shapes up.
ll = ll.scale(track_factor).floor() + self.track_offset
ur = ur.scale(track_factor).ceil() + self.track_offset
if ll.x<0:
ll.x=0
if ll.y<0:
ll.y=0
else: # Always round pin shapes down
ll = ll.scale(track_factor).round()
ur = ur.scale(track_factor).round()
debug.info(1,"Converting [ {0} , {1} ]".format(ll,ur))
ll=ll.scale(self.track_factor)
ur=ur.scale(self.track_factor)
ll = ll.floor() if round_bigger else ll.round()
ur = ur.ceil() if round_bigger else ur.round()
#debug.info(1,"Converted [ {0} , {1} ]".format(ll,ur))
return [ll,ur]
def convert_track_to_shape(self,tracks):
def convert_track_to_shape(self,track):
"""
Convert a set of track units into a rectangle shape.
Convert a grid point into a rectangle shape that occupies the centered
track.
"""
tracks = tracks - self.track_offset
# to scale coordinates to tracks
# FIXME: should be offset by spacing, not track width
x = tracks.x*self.track_width - 0.25*self.track_width
y = tracks.y*self.track_width - 0.25*self.track_width
# FIXME: should be the metal width no the track width?
x = track.x*self.track_width - 0.5*self.track_width
y = track.y*self.track_width - 0.5*self.track_width
# offset lowest corner object to to (-track halo,-track halo)
ll = snap_to_grid(vector(x,y))
ur = snap_to_grid(ll + vector(0.5*self.track_width,0.5*self.track_width))
ur = snap_to_grid(ll + vector(self.track_width,self.track_width))
return [ll,ur]

View File

@ -49,21 +49,11 @@ class no_blockages_test(unittest.TestCase):
self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),gdsname)
r=router.router(self.gdsname)
layer_stack =("metal1","via1","metal2")
(src_rect,path,dest_rect)=r.route(layer_stack,src="A",dest="B")
#r.rg.view()
self.add_rect(layer=layer_stack[0],
offset=src_rect[0],
width=src_rect[1].x-src_rect[0].x,
height=src_rect[1].y-src_rect[0].y)
self.add_wire(layer_stack,path)
self.add_rect(layer=layer_stack[0],
offset=dest_rect[0],
width=dest_rect[1].x-dest_rect[0].x,
height=dest_rect[1].y-dest_rect[0].y)
r.route(layer_stack,src="A",dest="B")
r.add_route(self)
r = routing("test1", "AB_no_blockages")
r = routing("test1", "01_no_blockages_test")
self.local_check(r)
# fails if there are any DRC errors on any cells

View File

@ -45,7 +45,7 @@ def auto_measure_libcell(pin_list, name, units, layer):
[cell["width"], cell["height"]] = measure_result
for pin in pin_list:
cell[str(pin)] = gds_pin_center(cell_vlsi.readPin(str(pin)))
cell[str(pin)] = gds_pin_center(cell_vlsi.readPinShape(str(pin)))
return cell