mirror of
https://github.com/VLSIDA/OpenRAM.git
synced 2026-09-08 12:03:27 +02:00
Add sketch for power grid routing code
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
class cell:
|
||||
"""
|
||||
A single cell that can be occupied in a given layer, blocked,
|
||||
visited, etc.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.visited = False
|
||||
self.path = False
|
||||
self.blocked = False
|
||||
self.source = False
|
||||
self.target = False
|
||||
# -1 means it isn't visited yet
|
||||
self.min_cost = -1
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Reset the dynamic info about routing. The pins/blockages are not reset so
|
||||
that they can be reused.
|
||||
"""
|
||||
self.visited=False
|
||||
self.min_cost=-1
|
||||
self.min_path=None
|
||||
self.blocked=False
|
||||
self.source=False
|
||||
self.target=False
|
||||
|
||||
|
||||
def get_type(self):
|
||||
if self.blocked:
|
||||
return "X"
|
||||
|
||||
if self.source:
|
||||
return "S"
|
||||
|
||||
if self.target:
|
||||
return "T"
|
||||
|
||||
if self.path:
|
||||
return "P"
|
||||
|
||||
# We can display the cost of the frontier
|
||||
if self.min_cost > 0:
|
||||
return self.min_cost
|
||||
|
||||
return None
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
import numpy as np
|
||||
import string
|
||||
from itertools import tee
|
||||
import debug
|
||||
from vector3d import vector3d
|
||||
from cell import cell
|
||||
import os
|
||||
|
||||
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. """
|
||||
|
||||
# costs are relative to a unit grid
|
||||
# non-preferred cost allows an off-direction jog of 1 grid
|
||||
# rather than 2 vias + preferred direction (cost 5)
|
||||
self.VIA_COST = 2
|
||||
self.NONPREFERRED_COST = 4
|
||||
self.PREFERRED_COST = 1
|
||||
|
||||
# list of the source/target grid coordinates
|
||||
self.source = []
|
||||
self.target = []
|
||||
|
||||
# let's leave the map sparse, cells are created on demand to reduce memory
|
||||
self.map={}
|
||||
|
||||
# priority queue for the maze routing
|
||||
self.q = Q.PriorityQueue()
|
||||
|
||||
def set_blocked(self,n):
|
||||
self.add_map(n)
|
||||
self.map[n].blocked=True
|
||||
|
||||
def is_blocked(self,n):
|
||||
self.add_map(n)
|
||||
return self.map[n].blocked
|
||||
|
||||
def set_source(self,n):
|
||||
self.add_map(n)
|
||||
self.map[n].source=True
|
||||
self.source.append(n)
|
||||
|
||||
def set_target(self,n):
|
||||
self.add_map(n)
|
||||
self.map[n].target=True
|
||||
self.target.append(n)
|
||||
|
||||
def reinit(self):
|
||||
""" Reinitialize everything for a new route. """
|
||||
|
||||
self.reset_cells()
|
||||
|
||||
# clear source and target pins
|
||||
self.source=[]
|
||||
self.target=[]
|
||||
|
||||
# clear the queue
|
||||
while (not self.q.empty()):
|
||||
self.q.get(False)
|
||||
|
||||
|
||||
def add_blockage_shape(self,ll,ur,z):
|
||||
debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z))
|
||||
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.set_blocked(n)
|
||||
|
||||
def add_blockage(self,block_list):
|
||||
debug.info(3,"Adding blockage list={0}".format(str(block_list)))
|
||||
for n in block_list:
|
||||
self.set_blocked(n)
|
||||
|
||||
def add_source(self,track_list):
|
||||
debug.info(3,"Adding source list={0}".format(str(track_list)))
|
||||
for n in track_list:
|
||||
if not self.is_blocked(n):
|
||||
self.set_source(n)
|
||||
|
||||
def add_target(self,track_list):
|
||||
debug.info(3,"Adding target list={0}".format(str(track_list)))
|
||||
for n in track_list:
|
||||
if not self.is_blocked(n):
|
||||
self.set_target(n)
|
||||
|
||||
def reset_cells(self):
|
||||
"""
|
||||
Reset the path and costs for all the grid cells.
|
||||
"""
|
||||
for p in self.map.values():
|
||||
p.reset()
|
||||
|
||||
|
||||
def add_path(self,path):
|
||||
"""
|
||||
Mark the path in the routing grid for visualization
|
||||
"""
|
||||
self.path=path
|
||||
for p in path:
|
||||
self.map[p].path=True
|
||||
|
||||
def route(self,detour_scale):
|
||||
"""
|
||||
This does the A* maze routing with preferred direction routing.
|
||||
"""
|
||||
|
||||
# We set a cost bound of the HPWL for run-time. This can be
|
||||
# over-ridden if the route fails due to pruning a feasible solution.
|
||||
cost_bound = detour_scale*self.cost_to_target(self.source[0])*self.PREFERRED_COST
|
||||
|
||||
# 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():
|
||||
# should we keep the path in the queue as well or just the final node?
|
||||
(cost,path) = self.q.get()
|
||||
debug.info(2,"Queue size: size=" + str(self.q.qsize()) + " " + str(cost))
|
||||
debug.info(3,"Expanding: cost=" + str(cost) + " " + str(path))
|
||||
|
||||
# expand the last element
|
||||
neighbors = self.expand_dirs(path)
|
||||
debug.info(3,"Neighbors: " + str(neighbors))
|
||||
|
||||
for n in neighbors:
|
||||
# node is added to the map by the expand routine
|
||||
newpath = path + [n]
|
||||
# check if we hit the target and are done
|
||||
if self.is_target(n):
|
||||
return (newpath,self.cost(newpath))
|
||||
elif not self.map[n].visited:
|
||||
# current path cost + predicted cost
|
||||
current_cost = self.cost(newpath)
|
||||
target_cost = self.cost_to_target(n)
|
||||
predicted_cost = current_cost + target_cost
|
||||
# only add the cost if it is less than our bound
|
||||
if (predicted_cost < cost_bound):
|
||||
if (self.map[n].min_cost==-1 or current_cost<self.map[n].min_cost):
|
||||
self.map[n].visited=True
|
||||
self.map[n].min_path = newpath
|
||||
self.map[n].min_cost = predicted_cost
|
||||
debug.info(3,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
|
||||
# add the cost to get to this point if we haven't reached it yet
|
||||
self.q.put((predicted_cost,newpath))
|
||||
|
||||
debug.warning("Unable to route path. Expand the detour_scale to allow detours.")
|
||||
return (None,None)
|
||||
|
||||
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)
|
||||
if not self.is_blocked(east) and not east in path:
|
||||
neighbors.append(east)
|
||||
|
||||
west= point + vector3d(-1,0,0)
|
||||
if not self.is_blocked(west) and not west in path:
|
||||
neighbors.append(west)
|
||||
|
||||
up = point + vector3d(0,0,1)
|
||||
if up.z<2 and not self.is_blocked(up) and not up in path:
|
||||
neighbors.append(up)
|
||||
|
||||
north = point + vector3d(0,1,0)
|
||||
if not self.is_blocked(north) and not north in path:
|
||||
neighbors.append(north)
|
||||
|
||||
south = point + vector3d(0,-1,0)
|
||||
if not self.is_blocked(south) and not south in path:
|
||||
neighbors.append(south)
|
||||
|
||||
down = point + vector3d(0,0,-1)
|
||||
if down.z>=0 and not self.is_blocked(down) 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.")
|
||||
|
||||
# uniquify the source (and target while we are at it)
|
||||
self.source = list(set(self.source))
|
||||
self.target = list(set(self.target))
|
||||
|
||||
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 hpwl(self, src, dest):
|
||||
"""
|
||||
Return half perimeter wire length from point to another.
|
||||
Either point can have positive or negative coordinates.
|
||||
Include the via penalty if there is one.
|
||||
"""
|
||||
hpwl = max(abs(src.x-dest.x),abs(dest.x-src.x))
|
||||
hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y))
|
||||
hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z))
|
||||
if src.x!=dest.x or src.y!=dest.y:
|
||||
hpwl += self.VIA_COST
|
||||
return hpwl
|
||||
|
||||
def cost_to_target(self,source):
|
||||
"""
|
||||
Find the cheapest HPWL distance to any target point ignoring
|
||||
blockages for A* search.
|
||||
"""
|
||||
cost = self.hpwl(source,self.target[0])
|
||||
for t in self.target:
|
||||
cost = min(self.hpwl(source,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.
|
||||
"""
|
||||
|
||||
# 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 self.PREFERRED_COST
|
||||
elif p0.y != p1.y: # vertical
|
||||
cost += self.NONPREFERRED_COST if (p0.z == 0) else self.PREFERRED_COST
|
||||
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
|
||||
@@ -0,0 +1,631 @@
|
||||
import gdsMill
|
||||
import tech
|
||||
from contact import contact
|
||||
import math
|
||||
import debug
|
||||
import grid
|
||||
from vector import vector
|
||||
from vector3d import vector3d
|
||||
from globals import OPTS
|
||||
|
||||
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
|
||||
|
||||
self.source_pin_shapes = []
|
||||
self.source_pin_zindex = None
|
||||
self.target_pin_shapes = []
|
||||
self.target_pin_zindex = None
|
||||
# the list of all blockage shapes
|
||||
self.blockages = []
|
||||
# all thepaths we've routed so far (to supplement the blockages)
|
||||
self.paths = []
|
||||
|
||||
# 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])
|
||||
|
||||
def set_top(self,top_name):
|
||||
""" 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. First layer
|
||||
is always horizontal, middle is via, and last is always
|
||||
vertical.
|
||||
"""
|
||||
self.layers = layers
|
||||
(horiz_layer, via_layer, vert_layer) = self.layers
|
||||
|
||||
self.vert_layer_name = vert_layer
|
||||
self.vert_layer_width = tech.drc["minwidth_{0}".format(vert_layer)]
|
||||
self.vert_layer_spacing = tech.drc[str(self.vert_layer_name)+"_to_"+str(self.vert_layer_name)]
|
||||
self.vert_layer_number = tech.layer[vert_layer]
|
||||
|
||||
self.horiz_layer_name = horiz_layer
|
||||
self.horiz_layer_width = tech.drc["minwidth_{0}".format(horiz_layer)]
|
||||
self.horiz_layer_spacing = tech.drc[str(self.horiz_layer_name)+"_to_"+str(self.horiz_layer_name)]
|
||||
self.horiz_layer_number = tech.layer[horiz_layer]
|
||||
|
||||
# Contacted track spacing.
|
||||
via_connect = contact(self.layers, (1, 1))
|
||||
self.max_via_size = max(via_connect.width,via_connect.height)
|
||||
self.horiz_track_width = self.max_via_size + self.horiz_layer_spacing
|
||||
self.vert_track_width = self.max_via_size + self.vert_layer_spacing
|
||||
|
||||
# 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))
|
||||
|
||||
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.
|
||||
"""
|
||||
# We will add a halo around the boundary
|
||||
# of this many tracks
|
||||
size = self.ur - self.ll
|
||||
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
|
||||
|
||||
self.rg = grid.grid()
|
||||
|
||||
|
||||
def find_pin(self,pin):
|
||||
"""
|
||||
Finds the pin shapes and converts to tracks.
|
||||
Pin can either be a label or a location,layer pair: [[x,y],layer].
|
||||
"""
|
||||
|
||||
if type(pin)==str:
|
||||
(pin_name,pin_layer,pin_shapes) = self.layout.getAllPinShapesByLabel(str(pin))
|
||||
else:
|
||||
(pin_name,pin_layer,pin_shapes) = self.layout.getAllPinShapesByLocLayer(pin[0],pin[1])
|
||||
|
||||
new_pin_shapes = []
|
||||
for pin_shape in pin_shapes:
|
||||
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
|
||||
new_pin_shapes.append([vector(pin_shape[0],pin_shape[1]),vector(pin_shape[2],pin_shape[3])])
|
||||
|
||||
debug.check(len(new_pin_shapes)>0,"Did not find any pin shapes for {0}.".format(str(pin)))
|
||||
|
||||
return (pin_layer,new_pin_shapes)
|
||||
|
||||
def find_blockages(self):
|
||||
"""
|
||||
Iterate through all the layers and write the obstacles to the routing grid.
|
||||
This doesn't consider whether the obstacles will be pins or not. They get reset later
|
||||
if they are not actually a blockage.
|
||||
"""
|
||||
for layer in self.layers:
|
||||
self.get_blockages(self.top_name)
|
||||
|
||||
|
||||
def clear_pins(self):
|
||||
"""
|
||||
Reset the source and destination pins to start a new routing.
|
||||
Convert the source/dest pins to blockages.
|
||||
Convert the routed path to blockages.
|
||||
Keep the other blockages unchanged.
|
||||
"""
|
||||
self.source_pin = None
|
||||
self.source_pin_shapes = []
|
||||
self.source_pin_zindex = None
|
||||
self.target_pin = None
|
||||
self.target_pin_shapes = []
|
||||
self.target_pin_zindex = None
|
||||
# DO NOT clear the blockages as these don't change
|
||||
self.rg.reinit()
|
||||
|
||||
|
||||
def route(self, cell, layers, src, dest, detour_scale=2):
|
||||
"""
|
||||
Route a single source-destination net and return
|
||||
the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route.
|
||||
This is used to speed up the routing when there is not much detouring needed.
|
||||
"""
|
||||
self.cell = cell
|
||||
|
||||
# Clear the pins if we have previously routed
|
||||
if (hasattr(self,'rg')):
|
||||
self.clear_pins()
|
||||
else:
|
||||
# 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()
|
||||
# This will get all shapes as blockages
|
||||
self.find_blockages()
|
||||
|
||||
# Get the pin shapes
|
||||
self.get_source(src)
|
||||
self.get_target(dest)
|
||||
|
||||
# Now add the blockages (all shapes except the src/tgt pins)
|
||||
self.add_blockages()
|
||||
# Add blockages from previous paths
|
||||
self.add_path_blockages()
|
||||
|
||||
# Now add the src/tgt if they are not blocked by other shapes
|
||||
self.add_source()
|
||||
self.add_target()
|
||||
|
||||
|
||||
# returns the path in tracks
|
||||
(path,cost) = self.rg.route(detour_scale)
|
||||
if path:
|
||||
debug.info(1,"Found path: cost={0} ".format(cost))
|
||||
debug.info(2,str(path))
|
||||
self.add_route(path)
|
||||
return True
|
||||
else:
|
||||
self.write_debug_gds()
|
||||
# clean up so we can try a reroute
|
||||
self.clear_pins()
|
||||
|
||||
|
||||
return False
|
||||
|
||||
def write_debug_gds(self,):
|
||||
"""
|
||||
Write out a GDS file with the routing grid and search information annotated on it.
|
||||
"""
|
||||
# Only add the debug info to the gds file if we have any debugging on.
|
||||
# This is because we may reroute a wire with detours and don't want the debug information.
|
||||
if OPTS.debug_level==0: return
|
||||
|
||||
self.add_router_info()
|
||||
debug.error("Writing debug_route.gds from {0} to {1}".format(self.source_pin,self.target_pin))
|
||||
self.cell.gds_write("debug_route.gds")
|
||||
|
||||
def add_router_info(self):
|
||||
"""
|
||||
Write the routing grid and router cost, blockage, pins on
|
||||
the boundary layer for debugging purposes. This can only be
|
||||
called once or the labels will overlap.
|
||||
"""
|
||||
debug.info(0,"Adding router info for {0} to {1}".format(self.source_pin,self.target_pin))
|
||||
grid_keys=self.rg.map.keys()
|
||||
partial_track=vector(0,self.track_width/6.0)
|
||||
for g in grid_keys:
|
||||
shape = self.convert_full_track_to_shape(g)
|
||||
self.cell.add_rect(layer="boundary",
|
||||
offset=shape[0],
|
||||
width=shape[1].x-shape[0].x,
|
||||
height=shape[1].y-shape[0].y)
|
||||
# These are the on grid pins
|
||||
#rect = self.convert_track_to_pin(g)
|
||||
#self.cell.add_rect(layer="boundary",
|
||||
# offset=rect[0],
|
||||
# width=rect[1].x-rect[0].x,
|
||||
# height=rect[1].y-rect[0].y)
|
||||
|
||||
t=self.rg.map[g].get_type()
|
||||
|
||||
# midpoint offset
|
||||
off=vector((shape[1].x+shape[0].x)/2,
|
||||
(shape[1].y+shape[0].y)/2)
|
||||
if g[2]==1:
|
||||
# Upper layer is upper right label
|
||||
type_off=off+partial_track
|
||||
else:
|
||||
# Lower layer is lower left label
|
||||
type_off=off-partial_track
|
||||
if t!=None:
|
||||
self.cell.add_label(text=str(t),
|
||||
layer="text",
|
||||
offset=type_off)
|
||||
self.cell.add_label(text="{0},{1}".format(g[0],g[1]),
|
||||
layer="text",
|
||||
offset=shape[0])
|
||||
|
||||
def add_route(self,path):
|
||||
"""
|
||||
Add the current wire route to the given design instance.
|
||||
"""
|
||||
debug.info(3,"Set path: " + str(path))
|
||||
|
||||
# Keep track of path for future blockages
|
||||
self.paths.append(path)
|
||||
|
||||
# This is marked for debug
|
||||
self.rg.add_path(path)
|
||||
|
||||
# For debugging... if the path failed to route.
|
||||
if False or path==None:
|
||||
self.write_debug_gds()
|
||||
|
||||
if 'Xout_4_1' in [self.source_pin, self.target_pin]:
|
||||
self.write_debug_gds()
|
||||
|
||||
|
||||
# First, simplify the path for
|
||||
#debug.info(1,str(self.path))
|
||||
contracted_path = self.contract_path(path)
|
||||
debug.info(1,str(contracted_path))
|
||||
|
||||
# Make sure there's a pin enclosure on the source and dest
|
||||
add_src_via = contracted_path[0].z!=self.source_pin_zindex
|
||||
self.add_grid_pin(contracted_path[0],add_src_via)
|
||||
add_tgt_via = contracted_path[-1].z!=self.target_pin_zindex
|
||||
self.add_grid_pin(contracted_path[-1],add_tgt_via)
|
||||
|
||||
# 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))
|
||||
self.cell.add_route(self.layers,abs_path)
|
||||
|
||||
|
||||
def add_grid_pin(self,point,add_via=False):
|
||||
"""
|
||||
Create a rectangle at the grid 3D point that is 1/2 DRC smaller
|
||||
than the routing grid on all sides.
|
||||
"""
|
||||
pin = self.convert_track_to_pin(point)
|
||||
self.cell.add_rect(layer=self.layers[2*point.z],
|
||||
offset=pin[0],
|
||||
width=pin[1].x-pin[0].x,
|
||||
height=pin[1].y-pin[0].y)
|
||||
|
||||
if add_via:
|
||||
# offset this by 1/2 the via size
|
||||
c=contact(self.layers, (1, 1))
|
||||
via_offset = vector(-0.5*c.width,-0.5*c.height)
|
||||
self.cell.add_via(self.layers,vector(point[0],point[1])+via_offset)
|
||||
|
||||
|
||||
def create_steiner_routes(self,pins):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
pass
|
||||
|
||||
def translate_coordinates(self, coord, mirr, angle, xyShift):
|
||||
"""
|
||||
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])
|
||||
y = (item[0]*math.sin(angle)+item[1]*mirr*math.cos(angle)+xyShift[1])
|
||||
coordinate += [(x, y)]
|
||||
return coordinate
|
||||
|
||||
def convert_shape_to_units(self, shape):
|
||||
"""
|
||||
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)
|
||||
return [ll,ur]
|
||||
|
||||
|
||||
def min_max_coord(self, coord):
|
||||
"""
|
||||
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])
|
||||
miny = min(coord[0][1], coord[1][1], coord[2][1], coord[3][1])
|
||||
maxy = max(coord[0][1], coord[1][1], coord[2][1], coord[3][1])
|
||||
coordinate += [vector(minx, miny)]
|
||||
coordinate += [vector(maxx, maxy)]
|
||||
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:
|
||||
return 0
|
||||
elif p0.y!=p1.y:
|
||||
return 1
|
||||
else:
|
||||
# z direction
|
||||
return 2
|
||||
|
||||
|
||||
def contract_path(self,path):
|
||||
"""
|
||||
Remove intermediate points in a rectilinear path.
|
||||
"""
|
||||
newpath = [path[0]]
|
||||
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])
|
||||
|
||||
# always add the last path
|
||||
newpath.append(path[-1])
|
||||
return newpath
|
||||
|
||||
|
||||
def add_path_blockages(self):
|
||||
"""
|
||||
Go through all of the past paths and add them as blockages.
|
||||
This is so we don't have to write/reload the GDS.
|
||||
"""
|
||||
for path in self.paths:
|
||||
for grid in path:
|
||||
self.rg.set_blocked(grid)
|
||||
|
||||
|
||||
def get_source(self,pin):
|
||||
"""
|
||||
Gets the source pin shapes only. Doesn't add to grid.
|
||||
"""
|
||||
self.source_pin = pin
|
||||
(self.source_pin_layer,self.source_pin_shapes) = self.find_pin(pin)
|
||||
zindex = 0 if self.source_pin_layer==self.horiz_layer_number else 1
|
||||
self.source_pin_zindex = zindex
|
||||
|
||||
def add_source(self):
|
||||
"""
|
||||
Mark the grids that are in the pin rectangle ranges to have the source property.
|
||||
pin can be a location or a label.
|
||||
"""
|
||||
|
||||
found_pin = False
|
||||
for shape in self.source_pin_shapes:
|
||||
(pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(shape,self.source_pin_zindex,self.source_pin)
|
||||
if (len(pin_in_tracks)>0): found_pin=True
|
||||
debug.info(1,"Set source: " + str(self.source_pin) + " " + str(pin_in_tracks) + " z=" + str(self.source_pin_zindex))
|
||||
self.rg.add_source(pin_in_tracks)
|
||||
self.rg.add_blockage(blockage_in_tracks)
|
||||
|
||||
if not found_pin:
|
||||
self.write_debug_gds()
|
||||
debug.check(found_pin,"Unable to find source pin on grid.")
|
||||
|
||||
def get_target(self,pin):
|
||||
"""
|
||||
Gets the target pin shapes only. Doesn't add to grid.
|
||||
"""
|
||||
self.target_pin = pin
|
||||
(self.target_pin_layer,self.target_pin_shapes) = self.find_pin(pin)
|
||||
zindex = 0 if self.target_pin_layer==self.horiz_layer_number else 1
|
||||
self.target_pin_zindex = zindex
|
||||
|
||||
def add_target(self):
|
||||
"""
|
||||
Mark the grids that are in the pin rectangle ranges to have the target property.
|
||||
pin can be a location or a label.
|
||||
"""
|
||||
found_pin=False
|
||||
for shape in self.target_pin_shapes:
|
||||
(pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(shape,self.target_pin_zindex,self.target_pin)
|
||||
if (len(pin_in_tracks)>0): found_pin=True
|
||||
debug.info(1,"Set target: " + str(self.target_pin) + " " + str(pin_in_tracks) + " z=" + str(self.target_pin_zindex))
|
||||
self.rg.add_target(pin_in_tracks)
|
||||
self.rg.add_blockage(blockage_in_tracks)
|
||||
|
||||
if not found_pin:
|
||||
self.write_debug_gds()
|
||||
debug.check(found_pin,"Unable to find target pin on grid.")
|
||||
|
||||
def add_blockages(self):
|
||||
""" Add the blockages except the pin shapes """
|
||||
for blockage in self.blockages:
|
||||
(shape,zlayer) = blockage
|
||||
# Skip source pin shapes
|
||||
if zlayer==self.source_pin_zindex and shape in self.source_pin_shapes:
|
||||
continue
|
||||
# Skip target pin shapes
|
||||
if zlayer==self.target_pin_zindex and shape in self.target_pin_shapes:
|
||||
continue
|
||||
[ll,ur]=self.convert_blockage_to_tracks(shape)
|
||||
self.rg.add_blockage_shape(ll,ur,zlayer)
|
||||
|
||||
|
||||
def get_blockages(self, sref, mirr = 1, angle = math.radians(float(0)), xyShift = (0, 0)):
|
||||
"""
|
||||
Recursive find 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]:
|
||||
zlayer = 0 if boundary.drawingLayer==self.horiz_layer_number else 1
|
||||
self.blockages.append((shape,zlayer))
|
||||
|
||||
|
||||
# recurse given the mirror, angle, etc.
|
||||
for cur_sref in self.layout.structures[sref].srefs:
|
||||
sMirr = 1
|
||||
if cur_sref.transFlags[0] == True:
|
||||
sMirr = -1
|
||||
sAngle = math.radians(float(0))
|
||||
if cur_sref.rotateAngle:
|
||||
sAngle = math.radians(float(cur_sref.rotateAngle))
|
||||
sAngle += angle
|
||||
x = cur_sref.coordinates[0]
|
||||
y = cur_sref.coordinates[1]
|
||||
newX = (x)*math.cos(angle) - mirr*(y)*math.sin(angle) + xyShift[0]
|
||||
newY = (x)*math.sin(angle) + mirr*(y)*math.cos(angle) + xyShift[1]
|
||||
sxyShift = (newX, newY)
|
||||
|
||||
self.get_blockages(cur_sref.sName, sMirr, sAngle, sxyShift)
|
||||
|
||||
def convert_point_to_units(self,p):
|
||||
"""
|
||||
Convert a path set of tracks to center line path.
|
||||
"""
|
||||
pt = vector3d(p)
|
||||
pt=pt.scale(self.track_widths[0],self.track_widths[1],1)
|
||||
return pt
|
||||
|
||||
def convert_blockage_to_tracks(self,shape,round_bigger=False):
|
||||
"""
|
||||
Convert a rectangular blockage shape into track units.
|
||||
"""
|
||||
[ll,ur] = shape
|
||||
ll = snap_to_grid(ll)
|
||||
ur = snap_to_grid(ur)
|
||||
|
||||
# to scale coordinates to tracks
|
||||
#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_pin_to_tracks(self,shape,zindex,pin):
|
||||
"""
|
||||
Convert a rectangular pin shape into a list of track locations,layers.
|
||||
If no on-grid pins are found, it searches for the nearest off-grid pin(s).
|
||||
If a pin has insufficent overlap, it returns the blockage list to avoid it.
|
||||
"""
|
||||
[ll,ur] = shape
|
||||
ll = snap_to_grid(ll)
|
||||
ur = snap_to_grid(ur)
|
||||
|
||||
#debug.info(1,"Converting [ {0} , {1} ]".format(ll,ur))
|
||||
|
||||
# scale the size bigger to include neaby tracks
|
||||
ll=ll.scale(self.track_factor).floor()
|
||||
ur=ur.scale(self.track_factor).ceil()
|
||||
|
||||
# width depends on which layer it is
|
||||
if zindex==0:
|
||||
width = self.horiz_layer_width
|
||||
else:
|
||||
width = self.vert_layer_width
|
||||
|
||||
track_list = []
|
||||
block_list = []
|
||||
# include +- 1 so when a shape is less than one grid
|
||||
for x in range(ll[0]-1,ur[0]+1):
|
||||
for y in range(ll[1]-1,ur[1]+1):
|
||||
#debug.info(1,"Converting [ {0} , {1} ]".format(x,y))
|
||||
# get the rectangular pin at a track location
|
||||
# if dimension of overlap is greater than min width in any dimension,
|
||||
# it will be an on-grid pin
|
||||
rect = self.convert_track_to_pin(vector3d(x,y,zindex))
|
||||
max_overlap=max(self.compute_overlap(shape,rect))
|
||||
|
||||
# however, if there is not enough overlap, then if there is any overlap at all,
|
||||
# we need to block it to prevent routes coming in on that grid
|
||||
full_rect = self.convert_full_track_to_shape(vector3d(x,y,zindex))
|
||||
full_overlap=max(self.compute_overlap(shape,full_rect))
|
||||
|
||||
#debug.info(1,"Check overlap: {0} {1} max={2}".format(shape,rect,max_overlap))
|
||||
if max_overlap >= width:
|
||||
track_list.append(vector3d(x,y,zindex))
|
||||
elif full_overlap>0:
|
||||
block_list.append(vector3d(x,y,zindex))
|
||||
else:
|
||||
debug.info(1,"No overlap: {0} {1} max={2}".format(shape,rect,max_overlap))
|
||||
|
||||
#debug.warning("Off-grid pin for {0}.".format(str(pin)))
|
||||
#debug.info(1,"Converted [ {0} , {1} ]".format(ll,ur))
|
||||
return (track_list,block_list)
|
||||
|
||||
def compute_overlap(self,r1,r2):
|
||||
""" Calculate the rectangular overlap of two rectangles. """
|
||||
(r1_ll,r1_ur) = r1
|
||||
(r2_ll,r2_ur) = r2
|
||||
|
||||
#ov_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y))
|
||||
#ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y))
|
||||
|
||||
dy = min(r1_ur.y,r2_ur.y)-max(r1_ll.y,r2_ll.y)
|
||||
dx = min(r1_ur.x,r2_ur.x)-max(r1_ll.x,r2_ll.x)
|
||||
|
||||
if dx>0 and dy>0:
|
||||
return [dx,dy]
|
||||
else:
|
||||
return [0,0]
|
||||
|
||||
|
||||
def convert_track_to_pin(self,track):
|
||||
"""
|
||||
Convert a grid point into a rectangle shape that is centered
|
||||
track in the track and leaves half a DRC space in each direction.
|
||||
"""
|
||||
# space depends on which layer it is
|
||||
if track[2]==0:
|
||||
space = 0.5*self.horiz_layer_spacing
|
||||
else:
|
||||
space = 0.5*self.vert_layer_spacing
|
||||
|
||||
# calculate lower left
|
||||
x = track.x*self.track_width - 0.5*self.track_width + space
|
||||
y = track.y*self.track_width - 0.5*self.track_width + space
|
||||
ll = snap_to_grid(vector(x,y))
|
||||
|
||||
# calculate upper right
|
||||
x = track.x*self.track_width + 0.5*self.track_width - space
|
||||
y = track.y*self.track_width + 0.5*self.track_width - space
|
||||
ur = snap_to_grid(vector(x,y))
|
||||
|
||||
return [ll,ur]
|
||||
|
||||
def convert_full_track_to_shape(self,track):
|
||||
"""
|
||||
Convert a grid point into a rectangle shape that occupies the entire centered
|
||||
track.
|
||||
"""
|
||||
# to scale coordinates to tracks
|
||||
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(self.track_width,self.track_width))
|
||||
|
||||
return [ll,ur]
|
||||
|
||||
|
||||
# FIXME: This should be replaced with vector.snap_to_grid at some point
|
||||
|
||||
def snap_to_grid(offset):
|
||||
"""
|
||||
Changes the coodrinate to match the grid settings
|
||||
"""
|
||||
grid = tech.drc["grid"]
|
||||
x = offset[0]
|
||||
y = offset[1]
|
||||
# this gets the nearest integer value
|
||||
xgrid = int(round(round((x / grid), 2), 0))
|
||||
ygrid = int(round(round((y / grid), 2), 0))
|
||||
xoff = xgrid * grid
|
||||
yoff = ygrid * grid
|
||||
return vector(xoff, yoff)
|
||||
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python2.7
|
||||
"Run a regresion test the library cells for DRC"
|
||||
|
||||
import unittest
|
||||
from testutils import header
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],"../.."))
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
import globals
|
||||
import debug
|
||||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
class no_blockages_test(unittest.TestCase):
|
||||
"""
|
||||
Simplest two pin route test with no blockages.
|
||||
"""
|
||||
|
||||
def runTest(self):
|
||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
global verify
|
||||
import verify
|
||||
|
||||
import design
|
||||
import router
|
||||
|
||||
class gdscell(design.design):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
#design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
self.name = name
|
||||
self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
design.hierarchy_layout.layout.__init__(self, name)
|
||||
design.hierarchy_spice.spice.__init__(self, name)
|
||||
|
||||
class routing(design.design,unittest.TestCase):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
|
||||
cell = gdscell(name)
|
||||
self.add_inst(name=name,
|
||||
mod=cell,
|
||||
offset=[0,0])
|
||||
self.connect_inst([])
|
||||
|
||||
self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
r=router.router(self.gdsname)
|
||||
layer_stack =("metal1","via1","metal2")
|
||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
|
||||
|
||||
r = routing("01_no_blockages_test_{0}".format(OPTS.tech_name))
|
||||
self.local_check(r)
|
||||
|
||||
# fails if there are any DRC errors on any cells
|
||||
globals.end_openram()
|
||||
|
||||
|
||||
def local_check(self, r):
|
||||
tempgds = OPTS.openram_temp + "temp.gds"
|
||||
r.gds_write(tempgds)
|
||||
self.assertFalse(calibre.run_drc(r.name, tempgds))
|
||||
os.remove(tempgds)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main()
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
|
||||
.SUBCKT cell
|
||||
.ENDS cell
|
||||
Binary file not shown.
Binary file not shown.
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python2.7
|
||||
"Run a regresion test the library cells for DRC"
|
||||
|
||||
import unittest
|
||||
from testutils import header
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],"../.."))
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
import globals
|
||||
import debug
|
||||
import calibre
|
||||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
class blockages_test(unittest.TestCase):
|
||||
"""
|
||||
Simple two pin route test with multilayer blockages.
|
||||
"""
|
||||
|
||||
def runTest(self):
|
||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
|
||||
import design
|
||||
import router
|
||||
|
||||
class gdscell(design.design):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
#design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
self.name = name
|
||||
self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
design.hierarchy_layout.layout.__init__(self, name)
|
||||
design.hierarchy_spice.spice.__init__(self, name)
|
||||
|
||||
class routing(design.design,unittest.TestCase):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
|
||||
cell = gdscell(name)
|
||||
self.add_inst(name=name,
|
||||
mod=cell,
|
||||
offset=[0,0])
|
||||
self.connect_inst([])
|
||||
|
||||
self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
r=router.router(self.gdsname)
|
||||
layer_stack =("metal1","via1","metal2")
|
||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
|
||||
|
||||
r = routing("02_blockages_test_{0}".format(OPTS.tech_name))
|
||||
self.local_check(r)
|
||||
|
||||
# fails if there are any DRC errors on any cells
|
||||
globals.end_openram()
|
||||
|
||||
|
||||
def local_check(self, r):
|
||||
tempgds = OPTS.openram_temp + "temp.gds"
|
||||
r.gds_write(tempgds)
|
||||
self.assertFalse(calibre.run_drc(r.name, tempgds))
|
||||
os.remove(tempgds)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main()
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
|
||||
.SUBCKT cell
|
||||
.ENDS cell
|
||||
Binary file not shown.
Binary file not shown.
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python2.7
|
||||
"Run a regresion test the library cells for DRC"
|
||||
|
||||
import unittest
|
||||
from testutils import header
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],"../.."))
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
import globals
|
||||
import debug
|
||||
import calibre
|
||||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
class same_layer_pins_test(unittest.TestCase):
|
||||
"""
|
||||
Checks two pins on the same layer with positive and negative coordinates.
|
||||
"""
|
||||
def runTest(self):
|
||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
|
||||
import design
|
||||
import router
|
||||
|
||||
class gdscell(design.design):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
#design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
self.name = name
|
||||
self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
design.hierarchy_layout.layout.__init__(self, name)
|
||||
design.hierarchy_spice.spice.__init__(self, name)
|
||||
|
||||
class routing(design.design,unittest.TestCase):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
|
||||
cell = gdscell(name)
|
||||
self.add_inst(name=name,
|
||||
mod=cell,
|
||||
offset=[0,0])
|
||||
self.connect_inst([])
|
||||
|
||||
self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
r=router.router(self.gdsname)
|
||||
layer_stack =("metal1","via1","metal2")
|
||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
|
||||
|
||||
r = routing("03_same_layer_pins_test_{0}".format(OPTS.tech_name))
|
||||
self.local_check(r)
|
||||
|
||||
|
||||
# fails if there are any DRC errors on any cells
|
||||
globals.end_openram()
|
||||
|
||||
|
||||
def local_check(self, r):
|
||||
tempgds = OPTS.openram_temp + "temp.gds"
|
||||
r.gds_write(tempgds)
|
||||
self.assertFalse(calibre.run_drc(r.name, tempgds))
|
||||
os.remove(tempgds)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
|
||||
.SUBCKT cell
|
||||
.ENDS cell
|
||||
Binary file not shown.
Binary file not shown.
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python2.7
|
||||
"Run a regresion test the library cells for DRC"
|
||||
|
||||
import unittest
|
||||
from testutils import header
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],"../.."))
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
import globals
|
||||
import debug
|
||||
import calibre
|
||||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
class diff_layer_pins_test(unittest.TestCase):
|
||||
"""
|
||||
Two pin route test with pins on different layers and blockages.
|
||||
Pins are smaller than grid size.
|
||||
"""
|
||||
|
||||
def runTest(self):
|
||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
|
||||
import design
|
||||
import router
|
||||
|
||||
class gdscell(design.design):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
#design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
self.name = name
|
||||
self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
design.hierarchy_layout.layout.__init__(self, name)
|
||||
design.hierarchy_spice.spice.__init__(self, name)
|
||||
|
||||
class routing(design.design,unittest.TestCase):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
|
||||
cell = gdscell(name)
|
||||
self.add_inst(name=name,
|
||||
mod=cell,
|
||||
offset=[0,0])
|
||||
self.connect_inst([])
|
||||
|
||||
self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
r=router.router(self.gdsname)
|
||||
layer_stack =("metal1","via1","metal2")
|
||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
|
||||
|
||||
r = routing("04_diff_layer_pins_test_{0}".format(OPTS.tech_name))
|
||||
self.local_check(r)
|
||||
|
||||
# fails if there are any DRC errors on any cells
|
||||
globals.end_openram()
|
||||
|
||||
|
||||
def local_check(self, r):
|
||||
tempgds = OPTS.openram_temp + "temp.gds"
|
||||
r.gds_write(tempgds)
|
||||
self.assertFalse(calibre.run_drc(r.name, tempgds))
|
||||
os.remove(tempgds)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
|
||||
.SUBCKT cell
|
||||
.ENDS cell
|
||||
Binary file not shown.
Binary file not shown.
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python2.7
|
||||
"Run a regresion test the library cells for DRC"
|
||||
|
||||
import unittest
|
||||
from testutils import header
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],"../.."))
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
import globals
|
||||
import debug
|
||||
import calibre
|
||||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
class two_nets_test(unittest.TestCase):
|
||||
"""
|
||||
Route two nets in the same GDS file. The routes will interact,
|
||||
so they must block eachother.
|
||||
"""
|
||||
|
||||
def runTest(self):
|
||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
|
||||
import design
|
||||
import router
|
||||
|
||||
class gdscell(design.design):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
#design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
self.name = name
|
||||
self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
design.hierarchy_layout.layout.__init__(self, name)
|
||||
design.hierarchy_spice.spice.__init__(self, name)
|
||||
|
||||
class routing(design.design,unittest.TestCase):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
|
||||
cell = gdscell(name)
|
||||
self.add_inst(name=name,
|
||||
mod=cell,
|
||||
offset=[0,0])
|
||||
self.connect_inst([])
|
||||
|
||||
self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
r=router.router(self.gdsname)
|
||||
layer_stack =("metal1","via1","metal2")
|
||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
|
||||
self.assertTrue(r.route(self,layer_stack,src="C",dest="D"))
|
||||
|
||||
r = routing("05_two_nets_test_{0}".format(OPTS.tech_name))
|
||||
self.local_check(r)
|
||||
|
||||
# fails if there are any DRC errors on any cells
|
||||
globals.end_openram()
|
||||
|
||||
|
||||
def local_check(self, r):
|
||||
tempgds = OPTS.openram_temp + "temp.gds"
|
||||
r.gds_write(tempgds)
|
||||
self.assertFalse(calibre.run_drc(r.name, tempgds))
|
||||
os.remove(tempgds)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main()
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
|
||||
.SUBCKT cell
|
||||
.ENDS cell
|
||||
Binary file not shown.
Binary file not shown.
Executable
+88
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python2.7
|
||||
"Run a regresion test the library cells for DRC"
|
||||
|
||||
import unittest
|
||||
from testutils import header
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],"../.."))
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
import globals
|
||||
import debug
|
||||
import calibre
|
||||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
class pin_location_test(unittest.TestCase):
|
||||
"""
|
||||
Simplest two pin route test with no blockages using the pin locations instead of labels.
|
||||
"""
|
||||
|
||||
def runTest(self):
|
||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
|
||||
import design
|
||||
import router
|
||||
|
||||
class gdscell(design.design):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
#design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
self.name = name
|
||||
self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
design.hierarchy_layout.layout.__init__(self, name)
|
||||
design.hierarchy_spice.spice.__init__(self, name)
|
||||
|
||||
class routing(design.design,unittest.TestCase):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
|
||||
cell = gdscell(name)
|
||||
self.add_inst(name=name,
|
||||
mod=cell,
|
||||
offset=[0,0])
|
||||
self.connect_inst([])
|
||||
|
||||
self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
r=router.router(self.gdsname)
|
||||
layer_stack =("metal1","via1","metal2")
|
||||
# these are user coordinates and layers
|
||||
src_pin = [[0.52, 4.099],11]
|
||||
tgt_pin = [[3.533, 1.087],11]
|
||||
#r.route(layer_stack,src="A",dest="B")
|
||||
self.assertTrue(r.route(self,layer_stack,src=src_pin,dest=tgt_pin))
|
||||
|
||||
# This only works for freepdk45 since the coordinates are hard coded
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
r = routing("06_pin_location_test_{0}".format(OPTS.tech_name))
|
||||
self.local_check(r)
|
||||
else:
|
||||
debug.warning("This test does not support technology {0}".format(OPTS.tech_name))
|
||||
|
||||
# fails if there are any DRC errors on any cells
|
||||
globals.end_openram()
|
||||
|
||||
|
||||
def local_check(self, r):
|
||||
tempgds = OPTS.openram_temp + "temp.gds"
|
||||
r.gds_write(tempgds)
|
||||
self.assertFalse(calibre.run_drc(r.name, tempgds))
|
||||
os.remove(tempgds)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main()
|
||||
Binary file not shown.
Binary file not shown.
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python2.7
|
||||
"Run a regresion test the library cells for DRC"
|
||||
|
||||
import unittest
|
||||
from testutils import header
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],"../.."))
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
import globals
|
||||
import debug
|
||||
import calibre
|
||||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
class big_test(unittest.TestCase):
|
||||
"""
|
||||
Simplest two pin route test with no blockages using the pin locations instead of labels.
|
||||
"""
|
||||
|
||||
def runTest(self):
|
||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
|
||||
import design
|
||||
import router
|
||||
|
||||
class gdscell(design.design):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
#design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
self.name = name
|
||||
self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
design.hierarchy_layout.layout.__init__(self, name)
|
||||
design.hierarchy_spice.spice.__init__(self, name)
|
||||
|
||||
class routing(design.design,unittest.TestCase):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
|
||||
cell = gdscell(name)
|
||||
self.add_inst(name=name,
|
||||
mod=cell,
|
||||
offset=[0,0])
|
||||
self.connect_inst([])
|
||||
|
||||
self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
r=router.router(self.gdsname)
|
||||
layer_stack =("metal3","via2","metal2")
|
||||
connections=[('out_0_2', 'a_0_0'),
|
||||
('out_0_3', 'b_0_0'),
|
||||
('out_0_0', 'a_0_1'),
|
||||
('out_1_2', 'a_1_0'),
|
||||
('out_1_3', 'b_1_0'),
|
||||
('out_1_0', 'a_1_1'),
|
||||
('out_2_1', 'a_2_0'),
|
||||
('out_2_2', 'b_2_0'),
|
||||
('out_3_1', 'a_3_0'),
|
||||
('out_3_2', 'b_3_0'),
|
||||
('out_4_6', 'a_4_0'),
|
||||
('out_4_7', 'b_4_0'),
|
||||
('out_4_8', 'a_4_2'),
|
||||
('out_4_9', 'b_4_2'),
|
||||
('out_4_10', 'a_4_4'),
|
||||
('out_4_11', 'b_4_4'),
|
||||
('out_4_0', 'a_4_1'),
|
||||
('out_4_2', 'b_4_1'),
|
||||
('out_4_4', 'a_4_5'),
|
||||
('out_4_1', 'a_4_3'),
|
||||
('out_4_5', 'b_4_3')]
|
||||
for (src,tgt) in connections:
|
||||
self.assertTrue(r.route(self,layer_stack,src=src,dest=tgt))
|
||||
|
||||
# This test only runs on scn3me_subm tech
|
||||
if OPTS.tech_name=="scn3me_subm":
|
||||
r = routing("07_big_test_{0}".format(OPTS.tech_name))
|
||||
self.local_check(r)
|
||||
else:
|
||||
debug.warning("This test does not support technology {0}".format(OPTS.tech_name))
|
||||
|
||||
# fails if there are any DRC errors on any cells
|
||||
globals.end_openram()
|
||||
|
||||
|
||||
def local_check(self, r):
|
||||
tempgds = OPTS.openram_temp + "temp.gds"
|
||||
r.gds_write(tempgds)
|
||||
self.assertFalse(calibre.run_drc(r.name, tempgds))
|
||||
os.remove(tempgds)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main()
|
||||
Binary file not shown.
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python2.7
|
||||
"Run a regresion test the library cells for DRC"
|
||||
|
||||
import unittest
|
||||
from testutils import header
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],"../.."))
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
import globals
|
||||
import debug
|
||||
import calibre
|
||||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
class expand_region_test(unittest.TestCase):
|
||||
"""
|
||||
Test an infeasible route followed by a feasible route with an expanded region.
|
||||
"""
|
||||
|
||||
def runTest(self):
|
||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
|
||||
import design
|
||||
import router
|
||||
|
||||
class gdscell(design.design):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
#design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
self.name = name
|
||||
self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
design.hierarchy_layout.layout.__init__(self, name)
|
||||
design.hierarchy_spice.spice.__init__(self, name)
|
||||
|
||||
class routing(design.design,unittest.TestCase):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "Create {0} object".format(name))
|
||||
|
||||
cell = gdscell(name)
|
||||
self.add_inst(name=name,
|
||||
mod=cell,
|
||||
offset=[0,0])
|
||||
self.connect_inst([])
|
||||
|
||||
self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
r=router.router(self.gdsname)
|
||||
layer_stack =("metal1","via1","metal2")
|
||||
# This should be infeasible because it is blocked without a detour.
|
||||
self.assertFalse(r.route(self,layer_stack,src="A",dest="B",detour_scale=1))
|
||||
# This should be feasible because we allow it to detour
|
||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B",detour_scale=3))
|
||||
|
||||
r = routing("08_expand_region_test_{0}".format(OPTS.tech_name))
|
||||
self.local_check(r)
|
||||
|
||||
# fails if there are any DRC errors on any cells
|
||||
globals.end_openram()
|
||||
|
||||
|
||||
def local_check(self, r):
|
||||
tempgds = OPTS.openram_temp + "temp.gds"
|
||||
r.gds_write(tempgds)
|
||||
self.assertFalse(calibre.run_drc(r.name, tempgds))
|
||||
os.remove(tempgds)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main()
|
||||
Binary file not shown.
Binary file not shown.
Executable
+23
@@ -0,0 +1,23 @@
|
||||
word_size = 1
|
||||
num_words = 16
|
||||
num_banks = 1
|
||||
|
||||
tech_name = "freepdk45"
|
||||
|
||||
decoder = "hierarchical_decoder"
|
||||
ms_flop = "ms_flop"
|
||||
ms_flop_array = "ms_flop_array"
|
||||
control_logic = "control_logic"
|
||||
bitcell_array = "bitcell_array"
|
||||
sense_amp = "sense_amp"
|
||||
sense_amp_array = "sense_amp_array"
|
||||
precharge_array = "precharge_array"
|
||||
column_mux_array = "single_level_column_mux_array"
|
||||
write_driver = "write_driver"
|
||||
write_driver_array = "write_driver_array"
|
||||
tri_gate = "tri_gate"
|
||||
tri_gate_array = "tri_gate_array"
|
||||
wordline_driver = "wordline_driver"
|
||||
replica_bitcell = "replica_bitcell"
|
||||
bitcell = "bitcell"
|
||||
delay_chain = "logic_effort_dc"
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
word_size = 1
|
||||
num_words = 16
|
||||
num_banks = 1
|
||||
|
||||
tech_name = "scn3me_subm"
|
||||
|
||||
decoder = "hierarchical_decoder"
|
||||
ms_flop = "ms_flop"
|
||||
ms_flop_array = "ms_flop_array"
|
||||
control_logic = "control_logic"
|
||||
bitcell_array = "bitcell_array"
|
||||
sense_amp = "sense_amp"
|
||||
sense_amp_array = "sense_amp_array"
|
||||
precharge_array = "precharge_array"
|
||||
column_mux_array = "single_level_column_mux_array"
|
||||
write_driver = "write_driver"
|
||||
write_driver_array = "write_driver_array"
|
||||
tri_gate = "tri_gate"
|
||||
tri_gate_array = "tri_gate_array"
|
||||
wordline_driver = "wordline_driver"
|
||||
replica_bitcell = "replica_bitcell"
|
||||
bitcell = "bitcell"
|
||||
delay_chain = "logic_effort_dc"
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
import unittest
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],"../../compiler"))
|
||||
print(sys.path)
|
||||
import globals
|
||||
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
|
||||
from testutils import header,openram_test
|
||||
header(__file__, OPTS.tech_name)
|
||||
|
||||
# get a list of all files in the tests directory
|
||||
files = os.listdir(sys.path[0])
|
||||
|
||||
# assume any file that ends in "test.py" in it is a regression test
|
||||
nametest = re.compile("test\.py$", re.IGNORECASE)
|
||||
tests = list(filter(nametest.search, files))
|
||||
tests.sort()
|
||||
|
||||
# import all of the modules
|
||||
filenameToModuleName = lambda f: os.path.splitext(f)[0]
|
||||
moduleNames = map(filenameToModuleName, tests)
|
||||
modules = map(__import__, moduleNames)
|
||||
suite = unittest.TestSuite()
|
||||
load = unittest.defaultTestLoader.loadTestsFromModule
|
||||
suite.addTests(map(load, modules))
|
||||
|
||||
test_runner = unittest.TextTestRunner(verbosity=2,stream=sys.stderr)
|
||||
test_result = test_runner.run(suite)
|
||||
|
||||
import verify
|
||||
verify.print_drc_stats()
|
||||
verify.print_lvs_stats()
|
||||
verify.print_pex_stats()
|
||||
|
||||
sys.exit(not test_result.wasSuccessful())
|
||||
Executable
+257
@@ -0,0 +1,257 @@
|
||||
import unittest,warnings
|
||||
import sys,os,glob,copy
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
from globals import OPTS
|
||||
import debug
|
||||
|
||||
class openram_test(unittest.TestCase):
|
||||
""" Base unit test that we have some shared classes in. """
|
||||
|
||||
def local_drc_check(self, w):
|
||||
|
||||
self.reset()
|
||||
|
||||
tempgds = OPTS.openram_temp + "temp.gds"
|
||||
w.gds_write(tempgds)
|
||||
import verify
|
||||
|
||||
result=verify.run_drc(w.name, tempgds)
|
||||
if result != 0:
|
||||
self.fail("DRC failed: {}".format(w.name))
|
||||
|
||||
self.cleanup()
|
||||
|
||||
def local_check(self, a, final_verification=False):
|
||||
|
||||
self.reset()
|
||||
|
||||
tempspice = OPTS.openram_temp + "temp.sp"
|
||||
tempgds = OPTS.openram_temp + "temp.gds"
|
||||
|
||||
a.sp_write(tempspice)
|
||||
a.gds_write(tempgds)
|
||||
|
||||
import verify
|
||||
result=verify.run_drc(a.name, tempgds)
|
||||
if result != 0:
|
||||
self.fail("DRC failed: {}".format(a.name))
|
||||
|
||||
|
||||
result=verify.run_lvs(a.name, tempgds, tempspice, final_verification)
|
||||
if result != 0:
|
||||
self.fail("LVS mismatch: {}".format(a.name))
|
||||
|
||||
if OPTS.purge_temp:
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
""" Reset the duplicate checker and cleanup files. """
|
||||
files = glob.glob(OPTS.openram_temp + '*')
|
||||
for f in files:
|
||||
# Only remove the files
|
||||
if os.path.isfile(f):
|
||||
os.remove(f)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Reset everything after each test.
|
||||
"""
|
||||
# Reset the static duplicate name checker for unit tests.
|
||||
import hierarchy_design
|
||||
hierarchy_design.hierarchy_design.name_map=[]
|
||||
|
||||
def check_golden_data(self, data, golden_data, error_tolerance=1e-2):
|
||||
"""
|
||||
This function goes through two dictionaries, key by key and compares
|
||||
each item. It uses relative comparisons for the items and returns false
|
||||
if there is a mismatch.
|
||||
"""
|
||||
|
||||
# Check each result
|
||||
data_matches = True
|
||||
for k in data.keys():
|
||||
if type(data[k])==list:
|
||||
for i in range(len(data[k])):
|
||||
if not self.isclose(k,data[k][i],golden_data[k][i],error_tolerance):
|
||||
data_matches = False
|
||||
else:
|
||||
self.isclose(k,data[k],golden_data[k],error_tolerance)
|
||||
if not data_matches:
|
||||
import pprint
|
||||
data_string=pprint.pformat(data)
|
||||
debug.error("Results exceeded {:.1f}% tolerance compared to golden results:\n".format(error_tolerance*100)+data_string)
|
||||
return data_matches
|
||||
|
||||
|
||||
|
||||
def isclose(self,key,value,actual_value,error_tolerance=1e-2):
|
||||
""" This is used to compare relative values. """
|
||||
import debug
|
||||
relative_diff = self.relative_diff(value,actual_value)
|
||||
check = relative_diff <= error_tolerance
|
||||
if check:
|
||||
debug.info(2,"CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key,value,actual_value,relative_diff*100))
|
||||
return True
|
||||
else:
|
||||
debug.error("NOT CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key,value,actual_value,relative_diff*100))
|
||||
return False
|
||||
|
||||
def relative_diff(self, value1, value2):
|
||||
""" Compute the relative difference of two values and normalize to the largest.
|
||||
If largest value is 0, just return the difference."""
|
||||
|
||||
# Edge case to avoid divide by zero
|
||||
if value1==0 and value2==0:
|
||||
return 0.0
|
||||
|
||||
# Don't need relative, exact compare
|
||||
if value1==value2:
|
||||
return 0.0
|
||||
|
||||
# Get normalization value
|
||||
norm_value = abs(max(value1, value2))
|
||||
# Edge case where greater is a zero
|
||||
if norm_value == 0:
|
||||
min_value = abs(min(value1, value2))
|
||||
|
||||
return abs(value1 - value2) / norm_value
|
||||
|
||||
|
||||
def relative_compare(self, value,actual_value,error_tolerance):
|
||||
""" This is used to compare relative values. """
|
||||
if (value==actual_value): # if we don't need a relative comparison!
|
||||
return True
|
||||
return (abs(value - actual_value) / max(value,actual_value) <= error_tolerance)
|
||||
|
||||
def isapproxdiff(self, filename1, filename2, error_tolerance=0.001):
|
||||
"""Compare two files.
|
||||
|
||||
Arguments:
|
||||
|
||||
filename1 -- First file name
|
||||
|
||||
filename2 -- Second file name
|
||||
|
||||
Return value:
|
||||
|
||||
True if the files are the same, False otherwise.
|
||||
|
||||
"""
|
||||
import re
|
||||
import debug
|
||||
|
||||
numeric_const_pattern = r"""
|
||||
[-+]? # optional sign
|
||||
(?:
|
||||
(?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc
|
||||
|
|
||||
(?: \d+ \.? ) # 1. 12. 123. etc 1 12 123 etc
|
||||
)
|
||||
# followed by optional exponent part if desired
|
||||
(?: [Ee] [+-]? \d+ ) ?
|
||||
"""
|
||||
rx = re.compile(numeric_const_pattern, re.VERBOSE)
|
||||
fp1 = open(filename1, 'rb')
|
||||
fp2 = open(filename2, 'rb')
|
||||
mismatches=0
|
||||
line_num=0
|
||||
while True:
|
||||
line_num+=1
|
||||
line1 = fp1.readline().decode('utf-8')
|
||||
line2 = fp2.readline().decode('utf-8')
|
||||
#print("line1:",line1)
|
||||
#print("line2:",line2)
|
||||
|
||||
# 1. Find all of the floats using a regex
|
||||
line1_floats=rx.findall(line1)
|
||||
line2_floats=rx.findall(line2)
|
||||
debug.info(3,"line1_floats: "+str(line1_floats))
|
||||
debug.info(3,"line2_floats: "+str(line2_floats))
|
||||
|
||||
|
||||
# 2. Remove the floats from the string
|
||||
for f in line1_floats:
|
||||
line1=line1.replace(f,"",1)
|
||||
for f in line2_floats:
|
||||
line2=line2.replace(f,"",1)
|
||||
#print("line1:",line1)
|
||||
#print("line2:",line2)
|
||||
|
||||
# 3. Convert to floats rather than strings
|
||||
line1_floats = [float(x) for x in line1_floats]
|
||||
line2_floats = [float(x) for x in line1_floats]
|
||||
|
||||
# 4. Check if remaining string matches
|
||||
if line1 != line2:
|
||||
if mismatches==0:
|
||||
debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2))
|
||||
mismatches += 1
|
||||
debug.error("MISMATCH Line ({0}):\n{1}\n!=\n{2}".format(line_num,line1.rstrip('\n'),line2.rstrip('\n')))
|
||||
|
||||
# 5. Now compare that the floats match
|
||||
elif len(line1_floats)!=len(line2_floats):
|
||||
if mismatches==0:
|
||||
debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2))
|
||||
mismatches += 1
|
||||
debug.error("MISMATCH Line ({0}) Length {1} != {2}".format(line_num,len(line1_floats),len(line2_floats)))
|
||||
else:
|
||||
for (float1,float2) in zip(line1_floats,line2_floats):
|
||||
relative_diff = self.relative_diff(float1,float2)
|
||||
check = relative_diff <= error_tolerance
|
||||
if not check:
|
||||
if mismatches==0:
|
||||
debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2))
|
||||
mismatches += 1
|
||||
debug.error("MISMATCH Line ({0}) Float {1} != {2} diff: {3:.1f}%".format(line_num,float1,float2,relative_diff*100))
|
||||
|
||||
# Only show the first 10 mismatch lines
|
||||
if not line1 and not line2 or mismatches>10:
|
||||
fp1.close()
|
||||
fp2.close()
|
||||
return mismatches==0
|
||||
|
||||
# Never reached
|
||||
return False
|
||||
|
||||
|
||||
def isdiff(self,filename1,filename2):
|
||||
""" This is used to compare two files and display the diff if they are different.. """
|
||||
import debug
|
||||
import filecmp
|
||||
import difflib
|
||||
check = filecmp.cmp(filename1,filename2)
|
||||
if not check:
|
||||
debug.error("MISMATCH file1={0} file2={1}".format(filename1,filename2))
|
||||
f1 = open(filename1,"r")
|
||||
s1 = f1.readlines().decode('utf-8')
|
||||
f1.close()
|
||||
f2 = open(filename2,"r").decode('utf-8')
|
||||
s2 = f2.readlines()
|
||||
f2.close()
|
||||
mismatches=0
|
||||
for line in difflib.unified_diff(s1, s2):
|
||||
mismatches += 1
|
||||
self.error("DIFF LINES:",line)
|
||||
if mismatches>10:
|
||||
return False
|
||||
return False
|
||||
else:
|
||||
debug.info(2,"MATCH {0} {1}".format(filename1,filename2))
|
||||
return True
|
||||
|
||||
def header(filename, technology):
|
||||
# Skip the header for gitlab regression
|
||||
import getpass
|
||||
if getpass.getuser() == "gitlab-runner":
|
||||
return
|
||||
|
||||
tst = "Running Test for:"
|
||||
print("\n")
|
||||
print(" ______________________________________________________________________________ ")
|
||||
print("|==============================================================================|")
|
||||
print("|=========" + tst.center(60) + "=========|")
|
||||
print("|=========" + technology.center(60) + "=========|")
|
||||
print("|=========" + filename.center(60) + "=========|")
|
||||
from globals import OPTS
|
||||
print("|=========" + OPTS.openram_temp.center(60) + "=========|")
|
||||
print("|==============================================================================|")
|
||||
@@ -0,0 +1,138 @@
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user