# -*- coding: utf-8 -*-
"""
Templates for automating the design of different wafer styles.
.. note::
Copyright 2009-2012 Lucas Heitzmann Gabrielli
Copyright 2013 Andrew G. Mark
gdsCAD (based on gdspy) is released under the terms of the GNU GPL
"""
from core import (Cell, CellArray, GdsImport, Elements)
from shapes import (Circle, Rectangle, Label)
from utils import rotate, translate
import os.path
import math
import numpy as np
import numbers
import string
[docs]class Wafer_GridStyle(Cell):
"""
The base wafer style consisting of blocks of patterned features.
:param name: The name of the new wafer cell
:param cells: a list of cells that will be tiled to fill the blocks
the cells will be cycled until all blocks are filled.
:block_gap: the distance to leave between blocks
:returns: A new wafer ``Cell``
Spacing between cells in a block is determined automatically based on the cell
bounding box, or by using the attribute cell.spacing if it is available.
"""
#wafer radius (in um)
wafer_r = None
#the block size in um
block_size = None
#the placement of the wafer alignment points
align_pts = None
def __init__(self, name, cells=None, block_gap=400):
Cell.__init__(self, name)
self.cells=cells
self.cell_layers=self._cell_layers()
self._label=None
self.edge_gap=block_gap/2.
def _cell_layers(self):
"""
A list of all active layers in ``cells``
"""
cell_layers=set()
for c in self.cells:
if isinstance(c, Cell):
cell_layers |= set(c.get_layers())
else:
for s in c:
cell_layers |= set(s.get_layers())
return list(cell_layers)
[docs] def add_aligment_marks(self):
"""
Create alignment marks on all active layers
"""
d_layers=self.cell_layers
styles=['A' if i%2 else 'C' for i in range(len(d_layers))]
am = AlignmentMarks(styles, d_layers)
ver = Verniers(styles, d_layers)
mag = 10.
mblock = Cell('WAF_ALGN_BLKS')
mblock.add(am, magnification=mag)
mblock.add(am, origin=(2300, -870))
mblock.add(ver, origin=(1700, -1500), magnification=3)
mblock.add(ver, origin=(2000, -1200))
mblock.add(ver, origin=(2500, -1200))
for pt in self.align_pts:
offset=np.array([3000, 2000]) * np.sign(pt)
self.add(mblock, origin=pt + offset)
[docs] def add_orientation_text(self):
"""
Create Orientation Label
"""
tblock = Cell('WAF_ORI_TEXT')
for l in self.cell_layers:
for (t, pt) in self.o_text.iteritems():
txt=Label(t, 1000, layer=l)
bbox=txt.bounding_box
width=np.array([1,0]) * (bbox[1,0]-bbox[0,0])
offset=width * (-1 if pt[0]<0 else 0)
txt.translate(np.array(pt) + offset)
tblock.add(txt)
self.add(tblock)
[docs] def add_dicing_marks(self):
"""
Create dicing marks
"""
width=100./2
r=self.wafer_r
rng=np.floor(self.wafer_r/self.block_size).astype(int)
dmarks=Cell('DIC_MRKS')
for l in self.cell_layers:
for x in np.arange(-rng[0], rng[0]+1)*self.block_size[0]:
y=np.sqrt(r**2-x**2)
vm=Rectangle((x-width, y), (x+width, -y), layer=l)
dmarks.add(vm)
for y in np.arange(-rng[1], rng[1]+1)*self.block_size[1]:
x=np.sqrt(r**2-y**2)
hm=Rectangle((x, y-width), (-x, y+width), layer=l)
dmarks.add(hm)
self.add(dmarks)
[docs] def add_wafer_outline(self):
"""
Create Wafer Outline
"""
outline=Cell('WAF_OLINE')
for l in self.cell_layers:
circ=Circle((0, 0), self.wafer_r, 100, layer=l)
outline.add(circ)
# outline.add(Disk(l, (0,0), self.wafer_r, self.wafer_r-10))
self.add(outline)
[docs] def add_blocks(self):
"""
Create blocks and add them to he wafer Cell
"""
self.manifest=''
for (i, pt) in enumerate(self.block_pts):
cell=self.cells[i % len(self.cells)]
origin = pt*self.block_size
prefix=self.blockcols[pt[0]]+self.blockrows[pt[1]]
if isinstance(cell, Cell):
cell_name=prefix+cell.name
try:
spacing = cell.spacing
except AttributeError:
spacing = None
block=Block(cell_name, cell, self.block_size, edge_gap=self.edge_gap, prefix=prefix+'_', spacing = spacing)
self.manifest+='%2d\t%s\t%s\t(%.2f, %.2f)\n' % ((i, prefix, cell.name)+tuple(origin))
else:
cell_name=prefix + cell[0].name
block=RangeBlock_1D(cell_name, cell, self.block_size, edge_gap=self.edge_gap, prefix=prefix+'_')
self.manifest+='%2d\t%s\t%s\t(%.2f, %.2f)\n' % ((i, prefix, cell[0].name)+tuple(origin))
self.add(block, origin=origin)
def _place_blocks(self):
"""
Create the list of valid block sites based on block size and wafer diam.
"""
ind_max=np.floor(self.wafer_r/self.block_size).astype(int)
self.block_pts=[]
for x in range(-ind_max[0], ind_max[0]):
for y in range(-ind_max[1], ind_max[1]):
origin=np.array([x,y])
flag=True
for corner in np.array([[0,0], [1,0], [1,1], [0,1]]):
lsq=(((origin+corner)*self.block_size)**2).sum()
if lsq > self.wafer_r**2:
flag=False
break
if flag:
self.block_pts.append([x,y])
#String prefixes to associate with each row/column index
xs, ys=set(), set()
for p in self.block_pts:
xs.add(p[0])
ys.add(p[1])
xs=sorted(list(xs))
self.blockcols=dict(zip(xs, [string.uppercase[i] for i,x in enumerate(xs)]))
ys=sorted(list(ys))
self.blockrows=dict(zip(ys, [string.digits[i] for i,y in enumerate(ys)]))
[docs] def add_label(self, label):
"""
Create a label
"""
if self._label is None:
self._label=Cell(self.name+'_LBL')
self.add(self._label)
else:
self._label.elements=[]
for l in self._cell_layers():
txt=Label(label, 1000, layer=l)
bbox=txt.bounding_box
offset=np.array([0,2]) * self.block_size - bbox[0] + 200
txt.translate(offset)
self._label.add(txt)
[docs]class Wafer_Style1(Wafer_GridStyle):
"""
A 2" wafer divided into 10mmx10mm squares
"""
def __init__(self, name, cells=None, block_gap=400, *args, **kwargs):
#wafer radius (in um)
self.wafer_r = 25.5e3
#the block size in um
self.block_size=np.array([10e3, 10e3])
#the placement of the wafer alignment points
self.align_pts=np.array([[1, 1],
[-1,1],
[-1,-1],
[1,-1]])*1e4
self.o_text={'UPPER RIGHT':(1.05e4, 1.4e4), 'UPPER LEFT':(-1.05e4,1.4e4),
'LOWER LEFT':(-1.05e4,-1.5e4), 'LOWER RIGHT':(1.05e4,-1.5e4)}
Wafer_GridStyle.__init__(self, name, cells, block_gap, *args, **kwargs)
self._place_blocks()
self.add_aligment_marks()
self.add_orientation_text()
self.add_dicing_marks()
self.add_wafer_outline()
self.add_blocks()
[docs]class Wafer_Style2(Wafer_GridStyle):
"""
A 2" wafer divided into 5mmx5mm squares
"""
def __init__(self, name, cells=None, block_gap=400):
#wafer radius (in um)
self.wafer_r = 25.5e3
#the block size in um
self.block_size=np.array([5e3, 5e3])
#the placement of the wafer alignment points
self.align_pts=np.array([[1,1],
[-1,1],
[-1,-1],
[1,-1]])*1e4
self.o_text={'UPPER RIGHT':(1.05e4, 1.35e4), 'UPPER LEFT':(-1.05e4,1.35e4),
'LOWER LEFT':(-1.05e4,-1.5e4), 'LOWER RIGHT':(1.05e4,-1.5e4)}
Wafer_GridStyle.__init__(self, name, cells, block_gap)
self._place_blocks()
blacklist=[[2,2], [2,3], [3,2], [2,-3], [2, -4], [3, -3],
[-3,2], [-3,3], [-4,2], [-3,-3], [-3,-4], [-4,-3]]
new_pts=[]
for pt in self.block_pts:
if pt not in blacklist:
new_pts.append(pt)
self.block_pts=new_pts
self.add_aligment_marks()
self.add_orientation_text()
self.add_dicing_marks()
self.add_wafer_outline()
self.add_blocks()
[docs]class Block(Cell):
"""
Creates a rectangular block with alignment marks, label, and many copies of the cell.
:param name: The block name
:param cell: The cell to tile within the block
:param size: the width and height in physical units of the block
:param spacing: 2D vector of the spacing between cells. When omitted spacing is
based on the cell bounding box.
:param edge_gap: distance to leave around the perimeter of the block
:param prefix: A string to add to the beginning of the cell name used for the
printed label, the address of the block location.
A block contains two alignment mark clusters (alignment marks + verniers)
at the bottom corners, a label based on the cell name, and a grid of many
copies of the cell.
"""
def __init__(self, name, cell, size,
spacing=None, edge_gap=0, prefix=''):
Cell.__init__(self, name)
size=np.asarray(size)
cell_layers=cell.get_layers()
d_layers=cell_layers
#Create alignment marks
styles=['A' if i%2 else 'C' for i in range(len(d_layers))]
am = AlignmentMarks(styles, d_layers)
ver = Verniers(styles, d_layers)
for e in ver.elements:
e.translate((310,-150))
am.add(e)
am_bbox = am.bounding_box
am_size = am_bbox[1]-am_bbox[0]
sp = size - am_size - edge_gap
self.add(CellArray(am, 2, 1, sp, -am_bbox[0]+0.5*edge_gap))
#Create text
for l in d_layers:
text=Label(prefix+cell.name, 150, (am_size[0]+edge_gap, +edge_gap), layer=l)
bbox=text.bounding_box
t_width = bbox[1,0]-bbox[0,0]
self.add(text)
bbox = cell.bounding_box
corner=bbox[0]
bbox = bbox[1]-bbox[0]
#Pattern reference cell
if spacing is None:
spacing= bbox*(1.5)
self.N=0
# upper area
cols=np.floor((size[0]-2*edge_gap + spacing[0])/spacing[0])
rows=np.floor((size[1]-am_size[1]-2*edge_gap)/spacing[1])
origin = np.ceil((am_size[1])/spacing[1])\
* spacing * np.array([0,1]) + edge_gap - corner
ar=CellArray(cell, cols, rows, spacing, origin)
self.add(ar)
self.N+=rows*cols
# lower area
cols=np.floor((size[0]-2*am_size[0]-t_width-2*edge_gap)/spacing[0])
rows=np.ceil(am_size[1]/spacing[1])
origin = np.ceil((am_size[0]+t_width)/spacing[0])\
* spacing * np.array([1,0]) + edge_gap - corner
ar=CellArray(cell, cols, rows, spacing, origin)
self.add(ar)
self.N+=rows*cols
[docs]class RangeBlock_1D(Cell):
"""
A block section for which the the artwork in cols varies
:param name: The cell name
:param cells: A list of cells to tile
:param size: the width and height in physical units of the block
:param spacing: 2D vector of the spacing between cells. When omitted spacing is
based on the cell bounding box.
:param edge_gap: distance to leave around the perimeter of the block
:param prefix: A string to add to the beginning of the cell name used for the
printed label, the address of the block location.
A block contains two alignment mark clusters (alignment marks + verniers)
at the bottom corners, a label based on the cell name, and a grid of many
copies of the cell.
"""
def __init__(self, name, cells, size, edge_gap=0, prefix=''):
Cell.__init__(self, name)
size=np.asarray(size)
cell_layers=set()
for c in cells:
cell_layers |= set(c.get_layers())
cell_layers=list(cell_layers)
d_layers=cell_layers
#Create alignment marks
styles=['A' if i%2 else 'C' for i in range(len(d_layers))]
am = AlignmentMarks(styles, d_layers)
ver = Verniers(styles, d_layers)
for e in ver.elements:
e.translate((310,-150))
am.add(e)
am_bbox=am.bounding_box
am_size=np.array([am_bbox[1,0]-am_bbox[0,0], am_bbox[1,1]-am_bbox[0,1]])
sp=size - am_size - edge_gap
self.add(CellArray(am, 2, 1, sp, -am_bbox[0]+0.5*edge_gap))
#Create text
for l in d_layers:
text=Label(prefix+cells[0].name, 150, (am_size[0]+edge_gap, +edge_gap), layer=l)
self.add(text)
bbox=text.bounding_box
t_width = bbox[1,0]-bbox[0,0]
#Pattern reference cells
spacings, corners, widths=[],[],[]
for c in cells:
bbox=c.bounding_box
corners.append(bbox[0])
bbox = np.array([bbox[1][0]-bbox[0][0], bbox[1][1]-bbox[0][1]])
spacings.append(bbox*1.5)
widths.append((bbox*1.5)[0])
self.N=0
origin = edge_gap * np.array([1,1])
n_cols=_divide_cols(size[0]-2*edge_gap, widths)
for (c, w, n, s, cr) in zip(cells, widths, n_cols, spacings, corners):
if ((origin[0]-cr[0])<(am_size[0]+t_width)) or ((origin[0]+n*s[0]) > (size[0]-am_size[0])):
origin[1]=am_size[1]+edge_gap
height=size[1]-2*edge_gap-am_size[1]
else:
origin[1]=edge_gap
height=size[1]-2*edge_gap
rows=np.floor(height/s[1])
ar=CellArray(c, n, rows, s, origin-cr)
self.add(ar)
self.N+=rows*n
origin += s[0] * n *np.array([1,0])
def _divide_cols(l, widths):
"""
Attempt to evenly divide the number of cols.
Try to ensure that:
-every type has at least one column
-types have roughly the same number of columns
-the array takes up as much width as possible
"""
widths=np.array(widths)
n_avg= np.floor(l / widths.sum())
ns=n_avg * np.ones(len(widths))
excess=l-(ns*widths).sum()
min_w=widths.min()
while excess>min_w:
for (i,w) in enumerate(widths):
if w<excess:
ns[i]+=1
excess-=w
return ns
[docs]class StripArray(Elements):
"""
Create a row of rectangular strips.
:param layer: the layer to add the edge to
:param start: the starting pt for the array of strips
:param end: the finish pt for the array of strips
:param size: the width and length of the strips
:param gap: the space between strips
:param angle: the amount by which to rotate the strips (0 is perp)
:param align: string indicating how to align the strips relative
center/top/bottom to the start-end line
:returns: Elements of the strips.
"""
def __init__(self, start, end, size, gap, angle=None, align='center', layer=None, datatype=None):
self.start=np.array(start)
self.end=np.array(end)
self.size=np.array(size)
self.gap=gap
self.align=align
pts=np.array([[0,0], [0, size[1]], size, [size[0], 0]])
if angle is not None:
pts=rotate(pts, angle, 'com')
if align.lower()=='bottom':
pass
elif align.lower()=='top':
pts=translate(pts, (0, -self.size[1]))
elif align.lower()=='center':
pts=translate(pts, (0, -self.size[1]/2))
else:
raise ValueError('Align parameter must be one of bottom/top/center')
strip_width=size[0]+gap
v=self.end-self.start
l=np.sqrt(np.dot(v,v))
N=int(np.floor(l/strip_width))
spacing=v/N
rotation=math.atan2(v[1], v[0])*180/np.pi
pts=rotate(pts, rotation)
origin = start + 0.5* v* (l-(N*strip_width - gap))/l
polys=[translate(pts, origin + i*spacing) for i in range(N)]
Elements.__init__(self, polys, layer, datatype)
[docs]def AlignmentMarks(styles, layers=1):
"""
Create alignment marks.
:param styles: a character, or a seq of characters indicating the style of
mark(s) desired
:param layers: an integer or a list of integers of layer(s) on which to
place the mark of the corresponding entry in ``styles``
:returns: A cell with the requested marks
Example::
# Make matching marks on layers 1 and 2
AlignmentMarks(('A', 'C'), (1,2))
The marks are stored in the file CONTACTALIGN.GDS
A:(layer1): 300 x 300 um
B:(layer2):
C:(layer3): 600x400um
"""
if isinstance(styles, numbers.Number): styles=[styles]
if isinstance(layers, numbers.Number):
layers=[layers]*len(styles)
else:
if len(layers)!=len(styles):
raise ValueError('Styles and layers must have same length.')
styles_dict={'A':1, 'B':2, 'C':3}
cell=Cell('CONT_ALGN')
path,_=os.path.split(__file__)
fname=os.path.join(path, 'resources', 'ALIGNMENT.GDS')
imp=GdsImport(fname)
for (s,l) in zip(styles, layers):
style=styles_dict[s]
for e in imp['CONTACTALIGN'].elements:
if e.layer==style:
new_e=e.copy()
new_e.layer=l
cell.add(new_e)
return cell
[docs]def Verniers(styles, layers=1):
"""
Create vernier alignment tools.
:param styles: a character 'A' or 'B', or a list of characters indicating
the style of mark(s) desired
:param layers: an integer or a list of integers of layer(s) on which to
place the mark of the corresponding entry in ``styles``
:returns: A cell with the requested marks
Example::
#Make a pair of matching verniers on layers 1 and 2
ver = Verniers(('A', 'B'), (1,2))
The marks are stored in the file VERNIERS.GDS
215 x 203 um
"""
if isinstance(styles, numbers.Number): styles=[styles]
if isinstance(layers, numbers.Number):
layers=[layers]*len(styles)
else:
if len(layers)!=len(styles):
raise ValueError('Styles and layers must have same length.')
styles_dict={'A':1, 'B':2, 'C':2}
cell=Cell('VERNIERS')
path,_=os.path.split(__file__)
fname=os.path.join(path, 'resources', 'ALIGNMENT.GDS')
imp=GdsImport(fname)
for (s,l) in zip(styles, layers):
style=styles_dict[s]
for e in imp['VERNIERS'].elements:
if e.layer==style:
new_e=e.copy()
new_e.layer=l
cell.add(new_e)
return cell