Add separate layer and purpose pairs to tech layers.

This commit is contained in:
Matt Guthaus 2019-10-25 10:03:25 -07:00
parent 31825d9d77
commit 38213d998f
10 changed files with 160 additions and 155 deletions

View File

@ -266,11 +266,12 @@ class instance(geometry):
class path(geometry):
"""Represents a Path"""
def __init__(self, layerNumber, coordinates, path_width):
def __init__(self, lpp, coordinates, path_width):
"""Initializes a path for the specified layer"""
geometry.__init__(self)
self.name = "path"
self.layerNumber = layerNumber
self.layerNumber = lpp[0]
self.layerPurpose = lpp[1]
self.coordinates = map(lambda x: [x[0], x[1]], coordinates)
self.coordinates = vector(self.coordinates).snap_to_grid()
self.path_width = path_width
@ -283,7 +284,7 @@ class path(geometry):
"""Writes the path to GDS"""
debug.info(4, "writing path (" + str(self.layerNumber) + "): " + self.coordinates)
new_layout.addPath(layerNumber=self.layerNumber,
purposeNumber=0,
purposeNumber=self.layerPurpose,
coordinates=self.coordinates,
width=self.path_width)
@ -303,12 +304,13 @@ class path(geometry):
class label(geometry):
"""Represents a text label"""
def __init__(self, text, layerNumber, offset, zoom=-1):
def __init__(self, text, lpp, offset, zoom=-1):
"""Initializes a text label for specified layer"""
geometry.__init__(self)
self.name = "label"
self.text = text
self.layerNumber = layerNumber
self.layerNumber = lpp[0]
self.layerPurpose = lpp[1]
self.offset = vector(offset).snap_to_grid()
if zoom<0:
@ -325,7 +327,7 @@ class label(geometry):
debug.info(4, "writing label (" + str(self.layerNumber) + "): " + self.text)
new_layout.addText(text=self.text,
layerNumber=self.layerNumber,
purposeNumber=0,
purposeNumber=self.layerPurpose,
offsetInMicrons=self.offset,
magnification=self.zoom,
rotate=None)
@ -346,11 +348,12 @@ class label(geometry):
class rectangle(geometry):
"""Represents a rectangular shape"""
def __init__(self, layerNumber, offset, width, height):
def __init__(self, lpp, offset, width, height):
"""Initializes a rectangular shape for specified layer"""
geometry.__init__(self)
self.name = "rect"
self.layerNumber = layerNumber
self.layerNumber = lpp[0]
self.layerPurpose = lpp[1]
self.offset = vector(offset).snap_to_grid()
self.size = vector(width, height).snap_to_grid()
self.width = round_to_grid(self.size.x)
@ -374,7 +377,7 @@ class rectangle(geometry):
debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):"
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
new_layout.addBox(layerNumber=self.layerNumber,
purposeNumber=0,
purposeNumber=self.layerPurpose,
offsetInMicrons=self.offset,
width=self.width,
height=self.height,

View File

@ -150,9 +150,9 @@ class layout():
if not height:
height=drc["minwidth_{}".format(layer)]
# negative layers indicate "unused" layers in a given technology
layer_num = techlayer[layer]
if layer_num >= 0:
self.objs.append(geometry.rectangle(layer_num, offset, width, height))
lpp = techlayer[layer]
if lpp[0] >= 0:
self.objs.append(geometry.rectangle(lpp, offset, width, height))
return self.objs[-1]
return None
@ -165,10 +165,10 @@ class layout():
if not height:
height=drc["minwidth_{}".format(layer)]
# negative layers indicate "unused" layers in a given technology
layer_num = techlayer[layer]
lpp = techlayer[layer]
corrected_offset = offset - vector(0.5*width,0.5*height)
if layer_num >= 0:
self.objs.append(geometry.rectangle(layer_num, corrected_offset, width, height))
if lpp[0] >= 0:
self.objs.append(geometry.rectangle(lpp, corrected_offset, width, height))
return self.objs[-1]
return None
@ -334,9 +334,9 @@ class layout():
"""Adds a text label on the given layer,offset, and zoom level"""
# negative layers indicate "unused" layers in a given technology
debug.info(5,"add label " + str(text) + " " + layer + " " + str(offset))
layer_num = techlayer[layer]
if layer_num >= 0:
self.objs.append(geometry.label(text, layer_num, offset, zoom))
lpp = techlayer[layer]
if lpp[0] >= 0:
self.objs.append(geometry.label(text, lpp, offset, zoom))
return self.objs[-1]
return None
@ -347,9 +347,9 @@ class layout():
import wire_path
# NOTE: (UNTESTED) add_path(...) is currently not used
# negative layers indicate "unused" layers in a given technology
#layer_num = techlayer[layer]
#if layer_num >= 0:
# self.objs.append(geometry.path(layer_num, coordinates, width))
#lpp = techlayer[layer]
#if lpp[0] >= 0:
# self.objs.append(geometry.path(lpp, coordinates, width))
wire_path.wire_path(obj=self,
layer=layer,
@ -539,7 +539,7 @@ class layout():
Do not write the pins since they aren't obstructions.
"""
if type(layer)==str:
layer_num = techlayer[layer]
layer_num = techlayer[layer][0]
else:
layer_num = layer

View File

@ -17,7 +17,7 @@ class pin_layout:
single shape.
"""
def __init__(self, name, rect, layer_name_num):
def __init__(self, name, rect, layer_name_pp):
self.name = name
# repack the rect as a vector, just in case
if type(rect[0])==vector:
@ -30,12 +30,19 @@ class pin_layout:
debug.check(self.width()>0,"Zero width pin.")
debug.check(self.height()>0,"Zero height pin.")
# if it's a layer number look up the layer name. this assumes a unique layer number.
if type(layer_name_num)==int:
self.layer = list(layer.keys())[list(layer.values()).index(layer_name_num)]
# if it's a string, use the name
if type(layer_name_pp)==str:
self.layer=layer_name_pp
# else it is required to be a lpp
else:
self.layer=layer_name_num
self.layer_num = layer[self.layer]
for (layer_name, lpp) in layer.items():
if layer_name_pp[0]==lpp[0] and (layer_name_pp[1]==None or layer_name_pp[1]==lpp[1]):
self.layer=layer_name
break
else:
debug.error("Couldn't find layer {}".format(layer_name_pp),-1)
self.lpp = layer[self.layer]
def __str__(self):
""" override print function output """
@ -310,8 +317,9 @@ class pin_layout:
"""Writes the pin shape and label to GDS"""
debug.info(4, "writing pin (" + str(self.layer) + "):"
+ str(self.width()) + "x" + str(self.height()) + " @ " + str(self.ll()))
newLayout.addBox(layerNumber=layer[self.layer],
purposeNumber=0,
(layer_num,purpose) = layer[self.layer]
newLayout.addBox(layerNumber=layer_num,
purposeNumber=purpose,
offsetInMicrons=self.ll(),
width=self.width(),
height=self.height(),
@ -319,8 +327,8 @@ class pin_layout:
# Add the tet in the middle of the pin.
# This fixes some pin label offsetting when GDS gets imported into Magic.
newLayout.addText(text=self.name,
layerNumber=layer[self.layer],
purposeNumber=0,
layerNumber=layer_num,
purposeNumber=purpose,
offsetInMicrons=self.center(),
magnification=GDS["zoom"],
rotate=None)

View File

@ -45,7 +45,7 @@ def pin_center(boundary):
"""
return [0.5 * (boundary[0] + boundary[2]), 0.5 * (boundary[1] + boundary[3])]
def auto_measure_libcell(pin_list, name, units, layer):
def auto_measure_libcell(pin_list, name, units, lpp):
"""
Open a GDS file and find the pins in pin_list as text on a given layer.
Return these as a set of properties including the cell width/height too.
@ -56,19 +56,19 @@ def auto_measure_libcell(pin_list, name, units, layer):
reader.loadFromFile(cell_gds)
cell = {}
measure_result = cell_vlsi.getLayoutBorder(layer)
measure_result = cell_vlsi.getLayoutBorder(lpp[0])
if measure_result == None:
measure_result = cell_vlsi.measureSize(name)
[cell["width"], cell["height"]] = measure_result
for pin in pin_list:
(name,layer,boundary)=cell_vlsi.getPinShapeByLabel(str(pin))
(name,lpp,boundary)=cell_vlsi.getPinShapeByLabel(str(pin))
cell[str(pin)] = pin_center(boundary)
return cell
def get_gds_size(name, gds_filename, units, layer):
def get_gds_size(name, gds_filename, units, lpp):
"""
Open a GDS file and return the size from either the
bounding box or a border layer.
@ -79,20 +79,20 @@ def get_gds_size(name, gds_filename, units, layer):
reader.loadFromFile(gds_filename)
cell = {}
measure_result = cell_vlsi.getLayoutBorder(layer)
measure_result = cell_vlsi.getLayoutBorder(lpp)
if measure_result == None:
debug.info(2,"Layout border failed. Trying to measure size for {}".format(name))
measure_result = cell_vlsi.measureSize(name)
# returns width,height
return measure_result
def get_libcell_size(name, units, layer):
def get_libcell_size(name, units, lpp):
"""
Open a GDS file and return the library cell size from either the
bounding box or a border layer.
"""
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
return(get_gds_size(name, cell_gds, units, layer))
return(get_gds_size(name, cell_gds, units, lpp))
def get_gds_pins(pin_names, name, gds_filename, units):
@ -109,10 +109,10 @@ def get_gds_pins(pin_names, name, gds_filename, units):
cell[str(pin_name)]=[]
pin_list=cell_vlsi.getPinShape(str(pin_name))
for pin_shape in pin_list:
(layer,boundary)=pin_shape
(lpp,boundary)=pin_shape
rect=[vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])]
# this is a list because other cells/designs may have must-connect pins
cell[str(pin_name)].append(pin_layout(pin_name, rect, layer))
cell[str(pin_name)].append(pin_layout(pin_name, rect, lpp))
return cell
def get_libcell_pins(pin_list, name, units):

View File

@ -686,10 +686,6 @@ class Gds2reader:
if idBits==('\x07','\x00'): break; #we've reached the end of the structure
elif(idBits==('\x06','\x06')):
structName = self.stripNonASCII(record[2::]) #(record[2:1] + record[1::]).rstrip()
# print(''.[x for x in structName if ord(x) < 128])
# stripped = (c for c in structName if 0 < ord(c) < 127)
# structName = "".join(stripped)
# print(self.stripNonASCII(structName)) ##FIXME: trimming by Tom g. ##could be an issue here with string trimming!
thisStructure.name = structName
if(findStructName==thisStructure.name):
wantedStruct=1
@ -767,10 +763,6 @@ class Gds2reader:
if idBits==('\x07','\x00'): break; #we've reached the end of the structure
elif(idBits==('\x06','\x06')):
structName = self.stripNonASCII(record[2::]) #(record[2:1] + record[1::]).rstrip()
# print(''.[x for x in structName if ord(x) < 128])
# stripped = (c for c in structName if 0 < ord(c) < 127)
# structName = "".join(stripped)
# print(self.stripNonASCIIx(structName)) ##FIXME: trimming by Tom g. ##could be an issue here with string trimming!
thisStructure.name = structName
if(self.debugToTerminal==1):
print("\tStructure Name: "+structName)

View File

@ -19,7 +19,8 @@ class VlsiLayout:
self.layerNumbersInUse = []
self.debug = False
if name:
self.rootStructureName=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
@ -82,13 +83,9 @@ class VlsiLayout:
return coordinatesRotate
def rename(self,newName):
#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]
self.structures[newName].name = newName
self.structures[newName].name = self.padText(newName)
#and delete the old root
del self.structures[self.rootStructureName]
self.rootStructureName = newName
@ -159,13 +156,14 @@ class VlsiLayout:
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
@ -219,7 +217,7 @@ class VlsiLayout:
self.populateCoordinateMap()
for layerNumber in self.layerNumbersInUse:
self.processLabelPins(layerNumber)
self.processLabelPins((layerNumber,None))
def populateCoordinateMap(self):
@ -400,10 +398,10 @@ class VlsiLayout:
cY = self.userUnits(coordinate[1])
layoutUnitCoordinates.append((cX,cY))
pathToAdd = GdsPath()
pathToAdd.drawingLayer=layerNumber
pathToAdd.drawingLayer = layerNumber
pathToAdd.purposeLayer = purposeNumber
pathToAdd.pathWidth=widthInLayoutUnits
pathToAdd.coordinates=layoutUnitCoordinates
pathToAdd.pathWidth = widthInLayoutUnits
pathToAdd.coordinates = layoutUnitCoordinates
#add the sref to the root structure
self.structures[self.rootStructureName].paths.append(pathToAdd)
@ -414,10 +412,8 @@ class VlsiLayout:
textToAdd.purposeLayer = purposeNumber
textToAdd.dataType = 0
textToAdd.coordinates = [offsetInLayoutUnits]
textToAdd.transFlags = [0,0,0]
if(len(text)%2 == 1):
text = text + '\x00'
textToAdd.textString = text
textToAdd.transFlags = [0,0,0]
textToAdd.textString = self.padText(text)
#textToAdd.transFlags[1] = 1
textToAdd.magFactor = magnification
if rotate:
@ -425,7 +421,13 @@ class VlsiLayout:
textToAdd.rotateAngle = rotate
#add the sref to the root structure
self.structures[self.rootStructureName].texts.append(textToAdd)
def padText(self, text):
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:
@ -591,22 +593,23 @@ class VlsiLayout:
passFailIndex += 1
print("Done\n\n")
def getLayoutBorder(self,borderlayer):
def getLayoutBorder(self,lpp):
cellSizeMicron=None
for boundary in self.structures[self.rootStructureName].boundaries:
if boundary.drawingLayer==borderlayer:
if boundary.drawingLayer==lpp[0] and \
(lpp[1]==None or boundary.purposeLayer==None or boundary.purposeLayer==lpp[1]):
if self.debug:
debug.info(1,"Find border "+str(boundary.coordinates))
left_bottom=boundary.coordinates[0]
right_top=boundary.coordinates[2]
cellSize=[right_top[0]-left_bottom[0],right_top[1]-left_bottom[1]]
cellSizeMicron=[cellSize[0]*self.units[0],cellSize[1]*self.units[0]]
if not(cellSizeMicron):
print("Error: "+str(self.rootStructureName)+".cell_size information not found yet")
debug.check(cellSizeMicron,
"Error: "+str(self.rootStructureName)+".cell_size information not found yet")
return cellSizeMicron
def measureSize(self,startStructure):
self.rootStructureName=startStructure
self.rootStructureName=self.padText(startStructure)
self.populateCoordinateMap()
cellBoundary = [None, None, None, None]
for TreeUnit in self.xyTree:
@ -616,7 +619,7 @@ class VlsiLayout:
return cellSizeMicron
def measureBoundary(self,startStructure):
self.rootStructureName=startStructure
self.rootStructureName=self.padText(startStructure)
self.populateCoordinateMap()
cellBoundary = [None, None, None, None]
for TreeUnit in self.xyTree:
@ -653,13 +656,13 @@ class VlsiLayout:
return cellBoundary
def getTexts(self, layer):
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 Text.drawingLayer == layer:
if Text.drawingLayer==lpp[0] and (lpp[1]==None or Text.purposeLayer==lpp[1]):
text_list.append(Text)
return text_list
@ -700,16 +703,16 @@ class VlsiLayout:
return shape_list
def processLabelPins(self, layer):
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(layer)
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(layer)
shapes = self.getAllShapes(lpp)
for label in labels:
label_coordinate = label.coordinates[0]
@ -717,7 +720,7 @@ class VlsiLayout:
pin_shapes = []
for boundary in shapes:
if self.labelInRectangle(user_coordinate,boundary):
pin_shapes.append((layer, boundary))
pin_shapes.append((lpp, boundary))
label_text = label.textString
# Remove the padding if it exists
@ -731,14 +734,14 @@ class VlsiLayout:
self.pins[label_text].append(pin_shapes)
def getBlockages(self,layer):
def getBlockages(self, lpp):
"""
Return all blockages on a given layer in [coordinate 1, coordinate 2,...] format and
user units.
"""
blockages = []
shapes = self.getAllShapes(layer)
shapes = self.getAllShapes(lpp)
for boundary in shapes:
vectors = []
for i in range(0,len(boundary),2):
@ -746,7 +749,7 @@ class VlsiLayout:
blockages.append(vectors)
return blockages
def getAllShapes(self,layer):
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.
@ -754,7 +757,7 @@ class VlsiLayout:
boundaries = set()
for TreeUnit in self.xyTree:
#print(TreeUnit[0])
boundaries.update(self.getShapesInStructure(layer,TreeUnit))
boundaries.update(self.getShapesInStructure(lpp,TreeUnit))
# Convert to user units
user_boundaries = []
@ -766,7 +769,7 @@ class VlsiLayout:
return user_boundaries
def getShapesInStructure(self,layer,structure):
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.
@ -775,7 +778,7 @@ class VlsiLayout:
#print(structureName,"u",structureuVector.transpose(),"v",structurevVector.transpose(),"o",structureOrigin.transpose())
boundaries = []
for boundary in self.structures[str(structureName)].boundaries:
if layer==boundary.drawingLayer:
if boundary.drawingLayer==lpp[0] and (lpp[1]==None or boundary.purposeLayer==lpp[1]):
if len(boundary.coordinates)!=5:
# if shape is a polygon (used in DFF)
boundaryPolygon = []

View File

@ -438,7 +438,6 @@ def import_tech():
OPTS.openram_tech = os.path.dirname(tech_mod.__file__) + "/"
# Add the tech directory
tech_path = OPTS.openram_tech
sys.path.append(tech_path)

View File

@ -35,39 +35,39 @@ GDS["zoom"] = 0.05
# create the GDS layer map
# FIXME: parse the gds layer map from the cadence map?
layer = {}
layer["active"] = 1
layer["pwell"] = 2
layer["nwell"] = 3
layer["nimplant"]= 4
layer["pimplant"]= 5
layer["vtg"] = 6
layer["vth"] = 7
layer["thkox"] = 8
layer["poly"] = 9
layer["contact"] = 10
layer["active_contact"] = 10
layer["metal1"] = 11
layer["via1"] = 12
layer["metal2"] = 13
layer["via2"] = 14
layer["metal3"] = 15
layer["via3"] = 16
layer["metal4"] = 17
layer["via4"] = 18
layer["metal5"] = 19
layer["via5"] = 20
layer["metal6"] = 21
layer["via6"] = 22
layer["metal7"] = 23
layer["via7"] = 24
layer["metal8"] = 25
layer["via8"] = 26
layer["metal9"] = 27
layer["via9"] = 28
layer["metal10"] = 29
layer["text"] = 239
layer["boundary"]= 239
layer["blockage"]= 239
layer["active"] = (1, 0)
layer["pwell"] = (2, 0)
layer["nwell"] = (3, 0)
layer["nimplant"]= (4, 0)
layer["pimplant"]= (5, 0)
layer["vtg"] = (6, 0)
layer["vth"] = (7, 0)
layer["thkox"] = (8, 0)
layer["poly"] = (9, 0)
layer["contact"] = (10, 0)
layer["active_contact"] = (10, 0)
layer["metal1"] = (11, 0)
layer["via1"] = (12, 0)
layer["metal2"] = (13, 0)
layer["via2"] = (14, 0)
layer["metal3"] = (15, 0)
layer["via3"] = (16, 0)
layer["metal4"] = (17, 0)
layer["via4"] = (18, 0)
layer["metal5"] = (19, 0)
layer["via5"] = (20, 0)
layer["metal6"] = (21, 0)
layer["via6"] = (22, 0)
layer["metal7"] = (23, 0)
layer["via7"] = (24, 0)
layer["metal8"] = (25, 0)
layer["via8"] = (26, 0)
layer["metal9"] = (27, 0)
layer["via9"] = (28, 0)
layer["metal10"] = (29, 0)
layer["text"] = (239, 0)
layer["boundary"]= (239, 0)
layer["blockage"]= (239, 0)
###################################################
##END GDS Layer Map

View File

@ -29,24 +29,24 @@ GDS["zoom"] = 0.5
# create the GDS layer map
layer={}
layer["vtg"] = -1
layer["vth"] = -1
layer["contact"] = 47
layer["pwell"] = 41
layer["nwell"] = 42
layer["active"] = 43
layer["pimplant"] = 44
layer["nimplant"] = 45
layer["poly"] = 46
layer["active_contact"] = 48
layer["metal1"] = 49
layer["via1"] = 50
layer["metal2"] = 51
layer["via2"] = 61
layer["metal3"] = 62
layer["text"] = 63
layer["boundary"] = 63
layer["blockage"] = 83
layer["vtg"] = (-1, 0)
layer["vth"] = (-1, 0)
layer["contact"] = (47, 0)
layer["pwell"] = (41, 0)
layer["nwell"] = (42, 0)
layer["active"] = (43, 0)
layer["pimplant"] = (44, 0)
layer["nimplant"] = (45, 0)
layer["poly"] = (46, 0)
layer["active_contact"] = (48, 0)
layer["metal1"] = (49, 0)
layer["via1"] = (50, 0)
layer["metal2"] = (51, 0)
layer["via2"] = (61, 0)
layer["metal3"] = (62, 0)
layer["text"] = (63, 0)
layer["boundary"] = (63, 0)
layer["blockage"] = (83, 0)
###################################################
##END GDS Layer Map

View File

@ -35,26 +35,26 @@ GDS["zoom"] = 0.5
# create the GDS layer map
layer={}
layer["vtg"] = -1
layer["vth"] = -1
layer["contact"] = 47
layer["pwell"] = 41
layer["nwell"] = 42
layer["active"] = 43
layer["pimplant"] = 44
layer["nimplant"] = 45
layer["poly"] = 46
layer["active_contact"] = 48
layer["metal1"] = 49
layer["via1"] = 50
layer["metal2"] = 51
layer["via2"] = 61
layer["metal3"] = 62
layer["via3"] = 30
layer["metal4"] = 31
layer["text"] = 63
layer["boundary"] = 63
layer["blockage"] = 83
layer["vtg"] = (-1, 0)
layer["vth"] = (-1, 0)
layer["contact"] = (47, 0)
layer["pwell"] = (41, 0)
layer["nwell"] = (42, 0)
layer["active"] = (43, 0)
layer["pimplant"] = (44, 0)
layer["nimplant"] = (45, 0)
layer["poly"] = (46, 0)
layer["active_contact"] = (48, 0)
layer["metal1"] = (49, 0)
layer["via1"] = (50, 0)
layer["metal2"] = (51, 0)
layer["via2"] = (61, 0)
layer["metal3"] = (62, 0)
layer["via3"] = (30, 0)
layer["metal4"] = (31, 0)
layer["text"] = (63, 0)
layer["boundary"] = (63, 0)
layer["blockage"] = (83, 0)
###################################################
##END GDS Layer Map