OpenRAM/compiler/gdsMill/gdsMill/vlsiLayout.py

971 lines
43 KiB
Python

from .gdsPrimitives import *
from datetime import *
import numpy as np
import math
import debug
class VlsiLayout:
"""Class represent a hierarchical layout"""
def __init__(self, name=None, units=(0.001,1e-9), libraryName="DEFAULT.DB", gdsVersion=5):
# keep a list of all the structures in this layout
self.units = units
# print(units)
modDate = datetime.now()
self.structures=dict()
self.layerNumbersInUse = []
self.debug = False
if name:
# take the root structure and copy it to a new structure with the new name
self.rootStructureName=self.padText(name)
# create the ROOT structure
self.structures[self.rootStructureName] = GdsStructure()
self.structures[self.rootStructureName].name = name
self.structures[self.rootStructureName].createDate = (modDate.year,
modDate.month,
modDate.day,
modDate.hour,
modDate.minute,
modDate.second)
self.structures[self.rootStructureName].modDate = (modDate.year,
modDate.month,
modDate.day,
modDate.hour,
modDate.minute,
modDate.second)
self.info = dict() # information gathered from the GDSII header
self.info['units']=self.units
self.info['dates']=(modDate.year,
modDate.month,
modDate.day,
modDate.hour,
modDate.minute,
modDate.second,
modDate.year,
modDate.month,
modDate.day,
modDate.hour,
modDate.minute,
modDate.second)
self.info['libraryName']=libraryName
self.info['gdsVersion']=gdsVersion
# This will contain a list of all structure names
# expanded to include srefs / arefs separately.
# each structure will have an X,Y,offset, and rotate associated
# with it. Populate via traverseTheHierarchy method.
self.xyTree = []
# temp variables used in delegate functions
self.tempCoordinates=None
self.tempPassFail = True
# This is a dict indexed by the pin labels.
# It contains a list of list of shapes, one for each occurance of the label.
# Multiple labels may be disconnected.
self.pins = {}
def rotatedCoordinates(self,coordinatesToRotate,rotateAngle):
# helper method to rotate a list of coordinates
angle=math.radians(float(0))
if(rotateAngle):
angle = math.radians(float(rotateAngle))
coordinatesRotate = [] # this will hold the rotated values
for coordinate in coordinatesToRotate:
# This is the CCW rotation matrix
newX = coordinate[0]*math.cos(angle) - coordinate[1]*math.sin(angle)
newY = coordinate[0]*math.sin(angle) + coordinate[1]*math.cos(angle)
coordinatesRotate.extend((newX,newY))
return coordinatesRotate
def uniquify(self):
new_structures = {}
if self.rootStructureName[-1] == "\x00":
prefix = self.rootStructureName[0:-1] + "_"
else:
prefix = self.rootStructureName + "_"
for name in self.structures:
if name[-1] == "\x00":
base_name = name[0:-1]
else:
base_name = name
if name != self.rootStructureName:
new_name = self.padText(prefix + base_name)
else:
new_name = name
#print("Structure: {0} -> {1}".format(base_name, new_name))
new_structures[new_name] = self.structures[name]
new_structures[new_name].name = new_name
for sref in new_structures[new_name].srefs:
if sref.sName[-1] == "\x00":
base_sref_name = sref.sName[0:-1]
else:
base_sref_name = sref.sName
new_sref_name = self.padText(prefix + base_sref_name)
sref.sName = new_sref_name
#print("SREF: {0} -> {1}".format(base_sref_name, new_sref_name))
self.structures = new_structures
def rename(self,newName):
# take the root structure and copy it to a new structure with the new name
self.structures[newName] = self.structures[self.rootStructureName]
self.structures[newName].name = self.padText(newName)
# and delete the old root
del self.structures[self.rootStructureName]
self.rootStructureName = newName
# repopulate the 2d map so drawing occurs correctly
del self.xyTree[:]
self.populateCoordinateMap()
def newLayout(self,newName):
# if (newName == "" | newName == 0):
# print("ERROR: vlsiLayout.py:newLayout newName is null")
# make sure the newName is a multiple of 2 characters
# if(len(newName)%2 == 1):
# pad with a zero
# newName = newName + '\x00'
# take the root structure and copy it to a new structure with the new name
# self.structures[newName] = self.structures[self.rootStructureName]
modDate = datetime.now()
self.structures[newName] = GdsStructure()
self.structures[newName].name = newName
self.rootStructureName = newName
self.rootStructureName=newName
# create the ROOT structure
self.structures[self.rootStructureName] = GdsStructure()
# self.structures[self.rootStructureName].name = name
self.structures[self.rootStructureName].createDate = (modDate.year,
modDate.month,
modDate.day,
modDate.hour,
modDate.minute,
modDate.second)
self.structures[self.rootStructureName].modDate = (modDate.year,
modDate.month,
modDate.day,
modDate.hour,
modDate.minute,
modDate.second)
#repopulate the 2d map so drawing occurs correctly
self.prepareForWrite()
def prepareForWrite(self):
del self.xyTree[:]
self.populateCoordinateMap()
def deduceHierarchy(self):
""" First, find the root of the tree.
Then go through and get the name of every structure.
Then, go through and find which structure is not
contained by any other structure. this is the root."""
structureNames=[]
for name in self.structures:
structureNames.append(name)
for name in self.structures:
if(len(self.structures[name].srefs)>0): #does this structure reference any others?
for sref in self.structures[name].srefs: #go through each reference
if sref.sName in structureNames: #and compare to our list
structureNames.remove(sref.sName)
debug.check(len(structureNames)==1,"Multiple possible root structures in the layout: {}".format(str(structureNames)))
self.rootStructureName = structureNames[0]
def traverseTheHierarchy(self, startingStructureName=None, delegateFunction=None,
transformPath=[], rotateAngle=0, transFlags=[0, 0, 0], coordinates=(0, 0)):
# since this is a recursive function, must deal with the default
# parameters explicitly
if startingStructureName == None:
startingStructureName = self.rootStructureName
# set up the rotation matrix
if(rotateAngle == None or rotateAngle == ""):
angle = 0
else:
# MRG: Added negative to make CCW rotate 8/29/18
angle = math.radians(float(rotateAngle))
mRotate = np.array([[math.cos(angle), -math.sin(angle), 0.0],
[math.sin(angle), math.cos(angle), 0.0],
[0.0, 0.0, 1.0]])
# set up the translation matrix
translateX = float(coordinates[0])
translateY = float(coordinates[1])
mTranslate = np.array([[1.0, 0.0, translateX],
[0.0, 1.0, translateY],
[0.0, 0.0, 1.0]])
# set up the scale matrix (handles mirror X)
scaleX = 1.0
if (transFlags[0]):
scaleY = -1.0
else:
scaleY = 1.0
mScale = np.array([[scaleX, 0.0, 0.0],
[0.0, scaleY, 0.0],
[0.0, 0.0, 1.0]])
# we need to keep track of all transforms in the hierarchy
# when we add an element to the xy tree, we apply all transforms from the bottom up
transformPath.append((mRotate,mScale,mTranslate))
if delegateFunction != None:
delegateFunction(startingStructureName, transformPath)
# starting with a particular structure, we will recursively traverse the tree
# ********might have to set the recursion level deeper for big layouts!
try:
if(len(self.structures[startingStructureName].srefs)>0): #does this structure reference any others?
# if so, go through each and call this function again
# if not, return back to the caller (caller can be this function)
for sref in self.structures[startingStructureName].srefs:
# here, we are going to modify the sref coordinates based on the parent objects rotation
self.traverseTheHierarchy(startingStructureName = sref.sName,
delegateFunction = delegateFunction,
transformPath = transformPath,
rotateAngle = sref.rotateAngle,
transFlags = sref.transFlags,
coordinates = sref.coordinates)
except KeyError:
debug.error("Could not find structure {} in GDS file.".format(startingStructureName),-1)
# MUST HANDLE AREFs HERE AS WELL
# when we return, drop the last transform from the transformPath
del transformPath[-1]
return
def initialize(self, special_purposes={}):
self.deduceHierarchy()
# self.traverseTheHierarchy()
self.populateCoordinateMap()
# only ones with text
for layerNumber in self.layerNumbersInUse:
# if layerNumber not in no_pin_shape:
if layerNumber in special_purposes:
self.processLabelPins((layerNumber, special_purposes[layerNumber]))
else:
self.processLabelPins((layerNumber, None))
def populateCoordinateMap(self):
def addToXyTree(startingStructureName = None,transformPath = None):
uVector = np.array([[1.0],[0.0],[0.0]]) #start with normal basis vectors
vVector = np.array([[0.0],[1.0],[0.0]])
origin = np.array([[0.0],[0.0],[1.0]]) #and an origin (Z component is 1.0 to indicate position instead of vector)
#make a copy of all the transforms and reverse it
reverseTransformPath = transformPath[:]
if len(reverseTransformPath) > 1:
reverseTransformPath.reverse()
#now go through each transform and apply them to our basis and origin in succession
for transform in reverseTransformPath:
origin = np.dot(transform[0], origin) #rotate
uVector = np.dot(transform[0], uVector) #rotate
vVector = np.dot(transform[0], vVector) #rotate
origin = np.dot(transform[1], origin) #scale
uVector = np.dot(transform[1], uVector) #scale
vVector = np.dot(transform[1], vVector) #scale
origin = np.dot(transform[2], origin) #translate
#we don't need to do a translation on the basis vectors
#uVector = transform[2] * uVector #translate
#vVector = transform[2] * vVector #translate
#populate the xyTree with each structureName and coordinate space
self.xyTree.append((startingStructureName,origin,uVector,vVector))
self.traverseTheHierarchy(delegateFunction = addToXyTree)
def microns(self, userUnits):
"""Utility function to convert user units to microns"""
userUnit = self.units[1]/self.units[0]
userUnitsPerMicron = userUnit / userunit
layoutUnitsPerMicron = userUnitsPerMicron / self.units[0]
return userUnits / layoutUnitsPerMicron
def userUnits(self, microns):
"""Utility function to convert microns to user units"""
userUnit = self.units[1]/self.units[0]
# userUnitsPerMicron = userUnit / 1e-6
userUnitsPerMicron = userUnit / (userUnit)
layoutUnitsPerMicron = userUnitsPerMicron / self.units[0]
# print("userUnit:",userUnit,
# "userUnitsPerMicron",userUnitsPerMicron,
# "layoutUnitsPerMicron",layoutUnitsPerMicron,
# [microns,microns*layoutUnitsPerMicron])
return round(microns*layoutUnitsPerMicron, 0)
def changeRoot(self,newRoot, create=False):
"""
Method to change the root pointer to another layout.
"""
if self.debug:
debug.info(0,"DEBUG: GdsMill vlsiLayout: changeRoot: %s "%newRoot)
# Determine if newRoot exists
# layoutToAdd (default) or nameOfLayout
if (newRoot == 0 | ((newRoot not in self.structures) & ~create)):
print("ERROR: vlsiLayout.changeRoot: Name of new root [%s] not found and create flag is false"%newRoot)
exit(1)
else:
if ((newRoot not in self.structures) & create):
self.newLayout(newRoot)
self.rootStructureName = newRoot
def addInstance(self,layoutToAdd,nameOfLayout=0,offsetInMicrons=(0,0),mirror=None,rotate=None):
"""
Method to insert one layout into another at a particular offset.
"""
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
if self.debug:
debug.info(0,"DEBUG: GdsMill vlsiLayout: addInstance: type {0}, nameOfLayout {1}".format(type(layoutToAdd),nameOfLayout))
debug.info(0,"DEBUG: name={0} offset={1} mirror={2} rotate={3}".format(layoutToAdd.rootStructureName,offsetInMicrons, mirror, rotate))
# Determine if we are instantiating the root design of
# layoutToAdd (default) or nameOfLayout
if nameOfLayout == 0:
StructureFound = True
StructureName = layoutToAdd.rootStructureName
else:
StructureName = nameOfLayout #layoutToAdd
StructureFound = False
for structure in layoutToAdd.structures:
if StructureName in structure:
if self.debug:
debug.info(1,"DEBUG: Structure %s Found"%StructureName)
StructureFound = True
debug.check(StructureFound,"Could not find layout to instantiate {}".format(StructureName))
# If layoutToAdd is a unique object (not this), then copy hierarchy,
# otherwise, if it is a text name of an internal structure, use it.
if layoutToAdd != self:
#first, we need to combine the structure dictionaries from both layouts
for structure in layoutToAdd.structures:
if structure not in self.structures:
self.structures[structure]=layoutToAdd.structures[structure]
#also combine the "layers in use" list
for layerNumber in layoutToAdd.layerNumbersInUse:
if layerNumber not in self.layerNumbersInUse:
self.layerNumbersInUse.append(layerNumber)
#add a reference to the new layout structure in this layout's root
layoutToAddSref = GdsSref()
layoutToAddSref.sName = StructureName
layoutToAddSref.coordinates = offsetInLayoutUnits
if mirror or rotate:
layoutToAddSref.transFlags = [0,0,0]
# transFlags = (mirror around x-axis, magnification, rotation)
# If magnification or rotation is true, it is the flags are then
# followed by an amount in the record
if mirror=="R90":
rotate = 90.0
if mirror=="R180":
rotate = 180.0
if mirror=="R270":
rotate = 270.0
if rotate:
#layoutToAddSref.transFlags[2] = 1
layoutToAddSref.rotateAngle = rotate
if mirror == "x" or mirror == "MX":
layoutToAddSref.transFlags[0] = 1
if mirror == "y" or mirror == "MY": #NOTE: "MY" option will override specified rotate angle
layoutToAddSref.transFlags[0] = 1
#layoutToAddSref.transFlags[2] = 1
layoutToAddSref.rotateAngle = 180.0
if mirror == "xy" or mirror == "XY": #NOTE: "XY" option will override specified rotate angle
#layoutToAddSref.transFlags[2] = 1
layoutToAddSref.rotateAngle = 180.0
#add the sref to the root structure
self.structures[self.rootStructureName].srefs.append(layoutToAddSref)
def addBox(self,layerNumber=0, purposeNumber=0, offsetInMicrons=(0,0), width=1.0, height=1.0,center=False):
"""
Method to add a box to a layout
"""
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
#print("addBox:offsetInLayoutUnits",offsetInLayoutUnits)
widthInLayoutUnits = self.userUnits(width)
heightInLayoutUnits = self.userUnits(height)
#print("offsetInLayoutUnits",widthInLayoutUnits,"heightInLayoutUnits",heightInLayoutUnits)
if not center:
coordinates=[offsetInLayoutUnits,
(offsetInLayoutUnits[0]+widthInLayoutUnits,offsetInLayoutUnits[1]),
(offsetInLayoutUnits[0]+widthInLayoutUnits,offsetInLayoutUnits[1]+heightInLayoutUnits),
(offsetInLayoutUnits[0],offsetInLayoutUnits[1]+heightInLayoutUnits),
offsetInLayoutUnits]
else:
startPoint = (offsetInLayoutUnits[0]-widthInLayoutUnits/2.0, offsetInLayoutUnits[1]-heightInLayoutUnits/2.0)
coordinates=[startPoint,
(startPoint[0]+widthInLayoutUnits,startPoint[1]),
(startPoint[0]+widthInLayoutUnits,startPoint[1]+heightInLayoutUnits),
(startPoint[0],startPoint[1]+heightInLayoutUnits),
startPoint]
boundaryToAdd = GdsBoundary()
boundaryToAdd.drawingLayer = layerNumber
boundaryToAdd.coordinates = coordinates
boundaryToAdd.purposeLayer = purposeNumber
#add the sref to the root structure
self.structures[self.rootStructureName].boundaries.append(boundaryToAdd)
def addPath(self, layerNumber=0, purposeNumber=0, coordinates=[(0,0)], width=1.0):
"""
Method to add a path to a layout
"""
widthInLayoutUnits = self.userUnits(width)
layoutUnitCoordinates = []
#first convert to proper units
for coordinate in coordinates:
cX = self.userUnits(coordinate[0])
cY = self.userUnits(coordinate[1])
layoutUnitCoordinates.append((cX,cY))
pathToAdd = GdsPath()
pathToAdd.drawingLayer = layerNumber
pathToAdd.purposeLayer = purposeNumber
pathToAdd.pathWidth = widthInLayoutUnits
pathToAdd.coordinates = layoutUnitCoordinates
#add the sref to the root structure
self.structures[self.rootStructureName].paths.append(pathToAdd)
def addText(self, text, layerNumber=0, purposeNumber=0, offsetInMicrons=(0,0), magnification=None, rotate=None):
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
textToAdd = GdsText()
textToAdd.drawingLayer = layerNumber
textToAdd.purposeLayer = purposeNumber
textToAdd.coordinates = [offsetInLayoutUnits]
textToAdd.transFlags = [0,0,0]
textToAdd.textString = self.padText(text)
#textToAdd.transFlags[1] = 1
if magnification:
textToAdd.magFactor = magnification
if rotate:
#textToAdd.transFlags[2] = 1
textToAdd.rotateAngle = rotate
#add the sref to the root structure
self.structures[self.rootStructureName].texts.append(textToAdd)
def padText(self, text):
debug.check(len(text) > 0, "Cannot have zero length text string.")
if(len(text) % 2 == 1):
return text + '\x00'
else:
return text
def isBounded(self,testPoint,startPoint,endPoint):
#these arguments are touples of (x,y) coordinates
if testPoint == None:
return 0
if(testPoint[0]<=max(endPoint[0],startPoint[0]) and \
testPoint[0]>=min(endPoint[0],startPoint[0]) and \
testPoint[1]<=max(endPoint[1],startPoint[1]) and \
testPoint[1]>=min(endPoint[1],startPoint[1])):
return 1
else:
return 0
def intersectionPoint(self,startPoint1,endPoint1,startPoint2,endPoint2):
if((endPoint1[0]-startPoint1[0])!=0 and (endPoint2[0]-startPoint2[0])!=0):
pSlope = (endPoint1[1]-startPoint1[1])/(endPoint1[0]-startPoint1[0])
pIntercept = startPoint1[1]-pSlope*startPoint1[0]
qSlope = (endPoint2[1]-startPoint2[1])/(endPoint2[0]-startPoint2[0])
qIntercept = startPoint2[1]-qSlope*startPoint2[0]
if(pSlope!=qSlope):
newX=(qIntercept-pIntercept)/(pSlope-qSlope)
newY=pSlope*newX+pIntercept
else:
#parallel lines can't intersect
newX=None
newY=None
elif((endPoint1[0]-startPoint1[0])==0 and (endPoint2[0]-startPoint2[0])==0):
#two vertical lines cannot intersect
newX = None
newY = None
elif((endPoint1[0]-startPoint1[0])==0 and (endPoint2[0]-startPoint2[0])!=0):
qSlope = (endPoint2[1]-startPoint2[1])/(endPoint2[0]-startPoint2[0])
qIntercept = startPoint2[1]-qSlope*startPoint2[0]
newX=endPoint1[0]
newY=qSlope*newX+qIntercept
elif((endPoint1[0]-startPoint1[0])!=0 and (endPoint2[0]-startPoint2[0])==0):
pSlope = (endPoint1[1]-startPoint1[1])/(endPoint1[0]-startPoint1[0])
pIntercept = startPoint1[1]-pSlope*startPoint1[0]
newX=endPoint2[0]
newY=pSlope*newX+pIntercept
return (newX,newY)
def isCollinear(self,testPoint,point1,point2):
slope1 = (testPoint[1]-point1[1])/(testPoint[0]-point1[0])
slope2 = (point2[1]-point1[1])/(point2[0]-point1[0])
if slope1 == slope2:
return True
return False
def doShapesIntersect(self,shape1Coordinates, shape2Coordinates):
"""
Utility function to determine if 2 arbitrary shapes intersect.
We define intersection by taking pairs of points in each shape (assuming they are in order)
and seeing if any of the lines formed by these pais intersect.
"""
for shape1Index in range(0,len(shape1Coordinates)-1):
for shape2Index in range(0,len(shape2Coordinates)-1):
startPoint1 = shape1Coordinates[shape1Index]
endPoint1 = shape1Coordinates[shape1Index+1]
startPoint2 = shape2Coordinates[shape2Index]
endPoint2 = shape2Coordinates[shape2Index+1]
intersect = self.intersectionPoint(startPoint1,endPoint1,startPoint2,endPoint2)
if(self.isBounded(intersect,startPoint1,endPoint1) and self.isBounded(intersect,startPoint2,endPoint2)):
return True #these shapes overlap!
return False #these shapes are ok
def isPointInsideOfBox(self,pointCoordinates,boxCoordinates):
"""
Check if a point is contained in the shape
"""
debug.check(len(boxCoordinates)==4,"Invalid number of coordinates for box.")
leftBound = boxCoordinates[0][0]
rightBound = boxCoordinates[0][0]
topBound = boxCoordinates[0][1]
bottomBound = boxCoordinates[0][1]
for point in boxCoordinates:
if point[0]<leftBound:
leftBound = point[0]
if point[0]>rightBound:
rightBound = point[0]
if point[1]<bottomBound:
bottomBound = point[1]
if point[1]>topBound:
topBound = point[1]
if(pointCoordinates[0]>rightBound or
pointCoordinates[0]<leftBound or
pointCoordinates[1]>topBound or
pointCoordinates[1]<bottomBound):
return False
return True
def isShapeInsideOfBox(self,shapeCoordinates, boxCoordinates):
"""
Go through every point in the shape to test if they are all inside the box.
"""
for point in shapeCoordinates:
if not self.isPointInsideOfBox(point,boxCoordinates):
return False
return True
def fillAreaDensity(self, layerToFill = 0, offsetInMicrons = (0,0), coverageWidth = 100.0, coverageHeight = 100.0, minSpacing = 0.22, blockSize = 1.0):
effectiveBlock = blockSize+minSpacing
widthInBlocks = int(coverageWidth/effectiveBlock)
heightInBlocks = int(coverageHeight/effectiveBlock)
passFailRecord = []
print("Filling layer:",layerToFill)
def isThisBlockOk(startingStructureName,coordinates,rotateAngle=None):
#go through every boundary and check
for boundary in self.structures[startingStructureName].boundaries:
#only test shapes on the same layer
if(boundary.drawingLayer == layerToFill):
#remap coordinates
shiftedBoundaryCoordinates = []
for shapeCoordinate in boundary.rotatedCoordinates(rotateAngle):
shiftedBoundaryCoordinates.append((shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1]))
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
if joint:
self.tempPassFail = False
common = self.isShapeInsideOfBox(shiftedBoundaryCoordinates,self.tempCoordinates)
if common:
self.tempPassFail = False
for path in self.structures[startingStructureName].paths:
#only test shapes on the same layer
if(path.drawingLayer == layerToFill):
#remap coordinates
shiftedBoundaryCoordinates = []
for shapeCoordinate in path.equivalentBoundaryCoordinates(rotateAngle):
shiftedBoundaryCoordinates.append((shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1]))
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
if joint:
self.tempPassFail = False
common = self.isShapeInsideOfBox(shiftedBoundaryCoordinates,self.tempCoordinates)
if common:
self.tempPassFail = False
for yIndex in range(0,heightInBlocks):
for xIndex in range(0,widthInBlocks):
percentDone = (float((yIndex*heightInBlocks)+xIndex) / (heightInBlocks*widthInBlocks))*100
blockX = (xIndex*effectiveBlock)+offsetInMicrons[0]
blockY = (yIndex*effectiveBlock)+offsetInMicrons[1]
self.tempCoordinates=[(self.userUnits(blockX-minSpacing),self.userUnits(blockY-minSpacing)),
(self.userUnits(blockX-minSpacing),self.userUnits(blockY+effectiveBlock)),
(self.userUnits(blockX+effectiveBlock),self.userUnits(blockY+effectiveBlock)),
(self.userUnits(blockX+effectiveBlock),self.userUnits(blockY-minSpacing)),
(self.userUnits(blockX-minSpacing),self.userUnits(blockY-minSpacing))]
self.tempPassFail = True
#go through the hierarchy and see if the block will fit
self.traverseTheHierarchy(delegateFunction = isThisBlockOk)
#if its bad, this global tempPassFail will be false
#if true, we can add the block
passFailRecord.append(self.tempPassFail)
print("Percent Complete:"+str(percentDone))
passFailIndex=0
for yIndex in range(0,heightInBlocks):
for xIndex in range(0,widthInBlocks):
blockX = (xIndex*effectiveBlock)+offsetInMicrons[0]
blockY = (yIndex*effectiveBlock)+offsetInMicrons[1]
if passFailRecord[passFailIndex]:
self.addBox(layerToFill, (blockX,blockY), width=blockSize, height=blockSize)
passFailIndex += 1
print("Done\n\n")
def getLayoutBorder(self, lpp, structure = ""):
if structure == "":
structure = self.rootStructureName
cellSizeMicron = None
shapes = self.getAllShapes(lpp)
if len(shapes) != 1:
debug.warning("More than one or no boundaries found in cell: {}".format(structure))
debug.check(len(shapes) != 0,
"Error: "+str(structure)+".cell_size information not found yet")
max_boundary = None
max_area = 0
for boundary in shapes:
new_area = boundaryArea(boundary)
if not max_boundary or new_area > max_area:
max_boundary = boundary
max_area = new_area
cellSizeMicron = [max_boundary[2], max_boundary[3]]
return cellSizeMicron
def measureSize(self, startStructure):
self.rootStructureName = self.padText(startStructure)
self.populateCoordinateMap()
cellBoundary = [None, None, None, None]
for TreeUnit in self.xyTree:
cellBoundary = self.measureSizeInStructure(TreeUnit, cellBoundary)
cellSize = [cellBoundary[2]-cellBoundary[0],
cellBoundary[3]-cellBoundary[1]]
cellSizeMicron = [cellSize[0]*self.units[0],
cellSize[1]*self.units[0]]
return cellSizeMicron
def measureBoundary(self, startStructure):
self.rootStructureName = self.padText(startStructure)
self.populateCoordinateMap()
cellBoundary = [None, None, None, None]
for TreeUnit in self.xyTree:
cellBoundary = self.measureSizeInStructure(TreeUnit, cellBoundary)
return [[self.units[0]*cellBoundary[0],
self.units[0]*cellBoundary[1]],
[self.units[0]*cellBoundary[2],
self.units[0]*cellBoundary[3]]]
def measureSizeInStructure(self, structure, cellBoundary):
(structureName, structureOrigin,
structureuVector, structurevVector) = structure
for boundary in self.structures[str(structureName)].boundaries:
left_bottom=boundary.coordinates[0]
right_top=boundary.coordinates[2]
thisBoundary=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]]
thisBoundary=self.transformRectangle(thisBoundary,structureuVector,structurevVector)
thisBoundary=[thisBoundary[0]+structureOrigin[0],thisBoundary[1]+structureOrigin[1],
thisBoundary[2]+structureOrigin[0],thisBoundary[3]+structureOrigin[1]]
cellBoundary=self.updateBoundary(thisBoundary,cellBoundary)
return cellBoundary
def updateBoundary(self,thisBoundary,cellBoundary):
[left_bott_X,left_bott_Y,right_top_X,right_top_Y]=thisBoundary
# If any are None
if not (cellBoundary[0] and cellBoundary[1] and cellBoundary[2] and cellBoundary[3]):
cellBoundary=thisBoundary
else:
if cellBoundary[0]>left_bott_X:
cellBoundary[0]=left_bott_X
if cellBoundary[1]>left_bott_Y:
cellBoundary[1]=left_bott_Y
if cellBoundary[2]<right_top_X:
cellBoundary[2]=right_top_X
if cellBoundary[3]<right_top_Y:
cellBoundary[3]=right_top_Y
return cellBoundary
def getTexts(self, lpp):
"""
Get all of the labels on a given layer only at the root level.
"""
text_list = []
for Text in self.structures[self.rootStructureName].texts:
if sameLPP((Text.drawingLayer, Text.purposeLayer),
lpp):
text_list.append(Text)
return text_list
def getPinShape(self, pin_name):
"""
Search for a pin label and return the largest enclosing rectangle
on the same layer as the pin label.
If there are multiple pin lists, return the max of each.
"""
pin_map = self.pins[pin_name]
max_pins = []
for pin_list in pin_map:
max_pin = None
max_area = 0
for pin in pin_list:
(layer, boundary) = pin
new_area = boundaryArea(boundary)
if not max_pin or new_area > max_area:
max_pin = pin
max_area = new_area
max_pins.append(max_pin)
return max_pins
def getAllPinShapes(self, pin_name):
"""
Search for a pin label and return ALL the enclosing rectangles on the same layer
as the pin label.
"""
shape_list = []
pin_map = self.pins[pin_name]
for pin_list in pin_map:
for pin in pin_list:
(pin_layer, boundary) = pin
shape_list.append(pin)
return shape_list
def processLabelPins(self, lpp):
"""
Find all text labels and create a map to a list of shapes that
they enclose on the given layer.
"""
# Get the labels on a layer in the root level
labels = self.getTexts(lpp)
# Get all of the shapes on the layer at all levels
# and transform them to the current level
shapes = self.getAllShapes(lpp)
for label in labels:
label_coordinate = label.coordinates[0]
user_coordinate = [x*self.units[0] for x in label_coordinate]
pin_shapes = []
# Remove the padding if it exists
if label.textString[-1] == "\x00":
label_text = label.textString[0:-1]
else:
label_text = label.textString
try:
from tech import layer_override
if layer_override[label_text]:
shapes = self.getAllShapes((layer_override[label_text][0], None))
if not shapes:
shapes = self.getAllShapes(lpp)
else:
lpp = layer_override[label_text]
except:
pass
for boundary in shapes:
if self.labelInRectangle(user_coordinate, boundary):
pin_shapes.append((lpp, boundary))
try:
self.pins[label_text]
except KeyError:
self.pins[label_text] = []
self.pins[label_text].append(pin_shapes)
def getBlockages(self, lpp):
"""
Return all blockages on a given layer in
[coordinate 1, coordinate 2,...] format and
user units.
"""
blockages = []
shapes = self.getAllShapes(lpp)
for boundary in shapes:
vectors = []
for i in range(0, len(boundary), 2):
vectors.append((boundary[i], boundary[i+1]))
blockages.append(vectors)
return blockages
def getAllShapes(self, lpp):
"""
Return all shapes on a given layer in [llx, lly, urx, ury]
format and user units for rectangles
and [coordinate 1, coordinate 2,...] format and user
units for polygons.
"""
boundaries = set()
for TreeUnit in self.xyTree:
# print(TreeUnit[0])
boundaries.update(self.getShapesInStructure(lpp, TreeUnit))
# Convert to user units
user_boundaries = []
for boundary in boundaries:
boundaries_list = []
for i in range(0, len(boundary)):
boundaries_list.append(boundary[i]*self.units[0])
user_boundaries.append(boundaries_list)
return user_boundaries
def getShapesInStructure(self, lpp, structure):
"""
Go through all the shapes in a structure and
return the list of shapes in
the form [llx, lly, urx, ury] for rectangles
and [coordinate 1, coordinate 2,...] for polygons.
"""
(structureName, structureOrigin,
structureuVector, structurevVector) = structure
# print(structureName,
# "u", structureuVector.transpose(),
# "v",structurevVector.transpose(),
# "o",structureOrigin.transpose())
boundaries = []
for boundary in self.structures[str(structureName)].boundaries:
if sameLPP((boundary.drawingLayer, boundary.purposeLayer),
lpp):
if len(boundary.coordinates) != 5:
# if shape is a polygon (used in DFF)
boundaryPolygon = []
# Polygon is a list of coordinates going ccw
for coord in range(0, len(boundary.coordinates)):
boundaryPolygon.append(boundary.coordinates[coord][0])
boundaryPolygon.append(boundary.coordinates[coord][1])
# perform the rotation
boundaryPolygon = self.transformPolygon(boundaryPolygon,
structureuVector,
structurevVector)
# add the offset
polygon = []
for i in range(0, len(boundaryPolygon), 2):
polygon.append(boundaryPolygon[i] + structureOrigin[0].item())
polygon.append(boundaryPolygon[i+1] + structureOrigin[1].item())
# make it a tuple
polygon = tuple(polygon)
boundaries.append(polygon)
else:
# else shape is a rectangle
left_bottom = boundary.coordinates[0]
right_top = boundary.coordinates[2]
# Rectangle is [leftx, bottomy, rightx, topy].
boundaryRect = [left_bottom[0], left_bottom[1],
right_top[0], right_top[1]]
# perform the rotation
boundaryRect = self.transformRectangle(boundaryRect,
structureuVector,
structurevVector)
# add the offset and make it a tuple
boundaryRect = (boundaryRect[0]+structureOrigin[0].item(),
boundaryRect[1]+structureOrigin[1].item(),
boundaryRect[2]+structureOrigin[0].item(),
boundaryRect[3]+structureOrigin[1].item())
boundaries.append(boundaryRect)
return boundaries
def transformPolygon(self,originalPolygon,uVector,vVector):
"""
Transforms the coordinates of a polygon in space.
"""
polygon = []
newPolygon = []
for i in range(0,len(originalPolygon),2):
polygon.append(self.transformCoordinate([originalPolygon[i],originalPolygon[i+1]],uVector,vVector))
newPolygon.append(polygon[int(i/2)][0])
newPolygon.append(polygon[int(i/2)][1])
return newPolygon
def transformRectangle(self,originalRectangle,uVector,vVector):
"""
Transforms the four coordinates of a rectangle in space
and recomputes the left, bottom, right, top values.
"""
leftBottom=[originalRectangle[0],originalRectangle[1]]
leftBottom=self.transformCoordinate(leftBottom,uVector,vVector)
rightTop=[originalRectangle[2],originalRectangle[3]]
rightTop=self.transformCoordinate(rightTop,uVector,vVector)
left=min(leftBottom[0],rightTop[0])
bottom=min(leftBottom[1],rightTop[1])
right=max(leftBottom[0],rightTop[0])
top=max(leftBottom[1],rightTop[1])
newRectangle = [left,bottom,right,top]
return newRectangle
def transformCoordinate(self,coordinate,uVector,vVector):
"""
Rotate a coordinate in space.
"""
# MRG: 9/3/18 Incorrect matrix multiplication!
# This is fixed to be:
# |u[0] v[0]| |x| |x'|
# |u[1] v[1]|x|y|=|y'|
x=coordinate[0]*uVector[0][0]+coordinate[1]*vVector[0][0]
y=coordinate[0]*uVector[1][0]+coordinate[1]*vVector[1][0]
transformCoordinate=[x,y]
return transformCoordinate
def labelInRectangle(self,coordinate,rectangle):
"""
Checks if a coordinate is within a given rectangle. Rectangle is [leftx, bottomy, rightx, topy].
"""
coordinate_In_Rectangle_x_range=(coordinate[0]>=rectangle[0])&(coordinate[0]<=rectangle[2])
coordinate_In_Rectangle_y_range=(coordinate[1]>=rectangle[1])&(coordinate[1]<=rectangle[3])
if coordinate_In_Rectangle_x_range & coordinate_In_Rectangle_y_range:
return True
else:
return False
def sameLPP(lpp1, lpp2):
"""
Check if the layers and purposes are the same.
Ignore if purpose is a None.
"""
if lpp1[1] == None or lpp2[1] == None:
return lpp1[0] == lpp2[0]
if isinstance(lpp1[1], list):
for i in range(len(lpp1[1])):
if lpp1[0] == lpp2[0] and lpp1[1][i] == lpp2[1]:
return True
if isinstance(lpp2[1], list):
for i in range(len(lpp2[1])):
if lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1][i]:
return True
return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1]
def boundaryArea(A):
"""
Returns boundary area for sorting.
"""
area_A=(A[2]-A[0])*(A[3]-A[1])
return area_A