OpenRAM/compiler/router/grid.py

268 lines
8.7 KiB
Python

import numpy as np
from PIL import Image
from itertools import tee
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
or horizontal layer.
"""
def __init__(self):
""" Create a routing map of width x height cells and 2 in the z-axis. """
self.NONPREFERRED_COST = 5
self.VIA_COST = 3
self.source = []
self.target = []
self.blocked = []
# let's leave the map sparse, cells are created on demand
self.map={}
# priority queue for the maze routing
self.q = Q.PriorityQueue()
# 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((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
# # so scale all visited indices by this value for colorization
# for x in range(self.width):
# for y in range(self.height):
# h_map[x,y] = self.map[vector3d(x,y,0)].get_color()
# v_map[x,y] = self.map[vector3d(x,y,1)].get_color()
# # This is just for scale
# if x==0 and y==0:
# h_map[x,y] = [0,0,0]
# v_map[x,y] = [0,0,0]
# v_img = Image.fromarray(v_map, 'RGB').rotate(90)
# #v_img.show()
# mid_img = Image.fromarray(mid_map, 'RGB').rotate(90)
# h_img = Image.fromarray(h_map, 'RGB').rotate(90)
# #h_img.show()
# # concatenate them into a plot with the two layers
# 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+10,0))
# #img.show()
# img.save(filename)
def set_property(self,ll,ur,z,name,value=True):
for x in range(int(ll[0]),int(ur[0])+1):
for y in range(int(ll[1]),int(ur[1])+1):
n = 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):
debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z))
self.set_property(ll,ur,z,"blocked")
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,"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,"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,cost_bound=0):
"""
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
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)
debug.info(4,"Neighbors: " + str(neighbors))
for n in neighbors:
newpath = path + [n]
if n not in self.map.keys():
self.map[n]=cell()
self.map[n].visited=True
# check if we hit the target and are done
if self.is_target(n):
return (newpath,self.cost(newpath))
else:
# potential path cost + predicted cost
cost = self.cost(newpath) + self.cost_to_target(n)
# 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)
def is_target(self,point):
"""
Point is in the target set, so we are done.
"""
return point in self.target
def expand_dirs(self,path):
"""
Expand each of the four cardinal directions plus up or down
but not expanding to blocked cells. Expands in all directions
regardless of preferred directions.
"""
# expand from the last point
point = path[-1]
neighbors = []
east = point + vector3d(1,0,0)
self.add_map(east)
if not self.map[east].blocked and not east in path:
neighbors.append(east)
west= point + vector3d(-1,0,0)
self.add_map(west)
if not self.map[west].blocked and not west in path:
neighbors.append(west)
up = point + vector3d(0,0,1)
self.add_map(up)
if up.z<2 and not self.map[up].blocked and not up in path:
neighbors.append(up)
north = point + vector3d(0,1,0)
self.add_map(north)
if not self.map[north].blocked and not north in path:
neighbors.append(north)
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)
self.add_map(down)
if down.z>=0 and not self.map[down].blocked and not down in path:
neighbors.append(down)
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):
"""
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(4,"Initializing queue.")
for s in self.source:
cost = self.cost_to_target(s)
debug.info(4,"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
def cost(self,path):
"""
The cost of the path is the length plus a penalty for the number
of vias.
We assume that non-preferred direction is penalized 2x.
"""
# Ignore the source pin layer change, FIXME?
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
plist = pairwise(path)
cost = 0
for p0,p1 in plist:
if p0.z != p1.z: # via
cost += self.VIA_COST
elif p0.x != p1.x: # horizontal
cost += self.NONPREFERRED_COST if (p0.z == 1) else 1
elif p0.y != p1.y: # vertical
cost += self.NONPREFERRED_COST if (p0.z == 0) else 1
else:
debug.error("Non-changing direction!")
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