Skip to content

Commit

Permalink
Added support for GC, and a spiral for SiN components in the beta Lib…
Browse files Browse the repository at this point in the history
…rary
  • Loading branch information
Hang-Bobby-Zou committed Aug 27, 2024
1 parent ada3345 commit 88b18a1
Show file tree
Hide file tree
Showing 2 changed files with 539 additions and 0 deletions.
319 changes: 319 additions & 0 deletions klayout/EBeam/pymacros/pcells_EBeam_Beta/AA_EBeam_GC_SiN_1310_12deg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
from pya import *
import pya

def linspace_without_numpy(low, high, length):
step = ((high-low) * 1.0 / length)
return [low+i*step for i in range(length)]

class AA_EBeam_GC_SiN_1310_12deg(pya.PCellDeclarationHelper):
"""
Universal Grating Coupler PCell implementation.
Analytical design based on "Grating Coupler Design Based on Silicon-On-Insulator", Yun Wang (2013). Master's Thesis, University of British Columbia, Canada
Some PCell implementation adapted from the SiEPIC_EBeam library by Dr. Lukas Chrostowski, University of British Columbia, Canada
Orignal script written by Timothy Richards (Simon Fraser University, BC, Canada) and Adam DeAbreu (Simon Fraser University, BC, Canada)
Modified to use UGC method presented in : Yun Wang, Jonas Flueckiger, Charlie Lin, Lukas Chrostowski, "Universal grating coupler design," Proc. SPIE 8915, Photonics North 2013, 89150Y (11 October 2013),
Transfered from Mentor Graphics to KLayout by Connor Mosquera (University of British Columbia, Canada)
Adapted by: Hang (Bobby) Zou
Date Edited: 2024-02-06
"""

def __init__(self):

# Important: initialize the super class
super(AA_EBeam_GC_SiN_1310_12deg, self).__init__()
from SiEPIC.utils import get_technology_by_name, load_Waveguides_by_Tech
TECHNOLOGY = get_technology_by_name('EBeam')
self.TECHNOLOGY = TECHNOLOGY

# declare the parameters
self.param("wavelength", self.TypeDouble, "Design Wavelength (micron)", default = 1.31)
self.param("Si_thickness", self.TypeDouble, "SiN Thickness (micron)", default = 0.4)
self.param("etch_depth", self.TypeDouble, "Etch Depth (micron)", default = 0.4)
self.param("pol", self.TypeString, "Polarization", default = "TE")
self.param("n_t", self.TypeDouble, "Cladding Index", default = 1.4718)
self.param("angle_e", self.TypeDouble, "Taper Angle (deg)", default = 36.0)
self.param("grating_length", self.TypeDouble, "Grating Length (micron)", default = 30.0)
self.param("taper_length", self.TypeDouble, "Taper Length (micron)", default = 20)
self.param("dc", self.TypeDouble, "Duty Cylce", default = 0.474)
self.param('pitch', self.TypeDouble, "Pitch (micron)", default = 0.956)
self.param("t", self.TypeDouble, "Waveguide Width (micron)", default = 0.75)
self.param("theta_c", self.TypeDouble, "Insertion Angle (deg)", default = 8.121)

# Layer parameters
self.param("layer", self.TypeLayer, "Layer", default = TECHNOLOGY['SiN'])
self.param("pinrec", self.TypeLayer, "PinRec Layer", default = TECHNOLOGY['PinRec'])
self.param("devrec", self.TypeLayer, "DevRec Layer", default = TECHNOLOGY['DevRec'])

def display_text_impl(self):
# Provide a descriptive text for the cell
# return "EBeam_GC_SiN_1310_8deg_%.1f-%.2f-%.2f-%.2f-%.2f-%.2f" % \
# (self.wavelength, self.theta_c, self.dc, self.angle_e, self.taper_length, self.t)
return "AA_EBeam_GC_SiN_1310_12deg"

def coerce_parameters_impl(self):
pass

def can_create_from_shape(self, layout, shape, layer):
return False

def produce_impl(self):
# fetch the parameters
dbu = self.layout.dbu
ly = self.layout
shapes = self.cell.shapes
TECHNOLOGY = self.TECHNOLOGY

LayerSi = self.layer
LayerSiN = ly.layer(LayerSi)
LayerSiSPN = ly.layer(LayerSi)
LayerPinRecN = ly.layer(self.pinrec)
LayerDevRecN = ly.layer(self.devrec)
LayerTextN = TECHNOLOGY['Text']

######## effective index function ##########

def effective_index(wl = self.wavelength, etch_depth = self.etch_depth, Si_thickness = self.Si_thickness, n_t = self.n_t, pol = self.pol, dc = self.dc):

from math import pi, cos, sin, log, sqrt, tan
from SiEPIC.utils import points_per_circle

point =1001
n_0 = n_t # Top cladding
n_1 = 0
n_3 = 1.4718 # Bottom Cladding
# if 0: # Silicon
# n_2 = sqrt(7.9874+(3.68*pow(3.9328,2)*pow(10,30))/((pow(3.9328,2)*pow(10,30)-pow(2*3.14*3*pow(10,8)/(wl*pow(10,-6)),2)))) # Silicon wavelength-dependant index of refraction
# if 1: # Silicon Nitride
n_2 = 2.001667 # approximate
delta = n_0 - n_3
t = Si_thickness
t_slot = t - etch_depth
k_0 = 2*pi/wl

b_0 = linspace_without_numpy(0, 0, point-1)
te_0 = linspace_without_numpy(0, 0, point-1)
te_1 = linspace_without_numpy(0, 0, point-1)
tm_0 = linspace_without_numpy(0, 0, point-1)
tm_1 = linspace_without_numpy(0, 0, point-1)
h_0 = linspace_without_numpy(0, 0, point-1)
q_0 = linspace_without_numpy(0, 0, point-1)
p_0 = linspace_without_numpy(0, 0, point-1)
qbar_0 = linspace_without_numpy(0, 0, point-1)
pbar_0 = linspace_without_numpy(0, 0, point-1)

# calculating neff for the silicon layer
if delta<0:
n_1 = n_3
else:
n_1 = n_0

for ii in range(0,point-1):
b_0[ii] = n_1*k_0 + (n_2 - n_1)*k_0/(point-1)*ii # copied from .ample UGC: should this be point-1?

h_0[ii] = sqrt( abs(pow(n_2*k_0,2) - pow(b_0[ii],2)));
q_0[ii] = sqrt( abs(pow(b_0[ii],2) - pow(n_0*k_0,2)));
p_0[ii] = sqrt( abs(pow(b_0[ii],2) - pow(n_3*k_0,2)));

pbar_0[ii] = pow(n_2/n_3, 2)*p_0[ii]
qbar_0[ii] = pow(n_2/n_0, 2)*q_0[ii]

# calculating neff for TE mode
if pol == "TE":
for ii in range(0, point-1):
te_0[ii] = tan( h_0[ii]*t )-(p_0[ii]+q_0[ii])/h_0[ii]/(1-p_0[ii]*q_0[ii]/pow(h_0[ii],2))
te_1[ii] = tan( h_0[ii]*t_slot )-(p_0[ii]+q_0[ii])/h_0[ii]/(1-p_0[ii]*q_0[ii]/pow(h_0[ii],2))

abs_te_0 = [abs(x) for x in te_0]
abs_te_1 = [abs(x) for x in te_1]
index_TE_0 = abs_te_0.index(min(abs_te_0))
index_TE_1 = abs_te_1.index(min(abs_te_1))
nTE_0 = b_0[index_TE_0]/k_0
nTE_1 = b_0[index_TE_1]/k_0
nTE_1 = n_3

while (nTE_0<1.5 or nTE_0>3):
abs_te_0[index_TE_0] = 100
index_TE_0 = abs_te_0.index(min(abs_te_0))
nTE_0 = b_0[index_TE_0]/k_0

'''
while (nTE_1<1.5 or nTE_1>3):
abs_te_1[index_TE_1] = 100
index_TE_1 = abs_te_1.index(min(abs_te_1))
nTE_1 = b_0[index_TE_1]/k_0
'''

ne=dc*nTE_0+(1-dc)*nTE_1

# calculating neff for TE mode
elif pol == "TM":
for ii in range(0, point-1):
tm_0[ii] = tan( h_0[ii]*t )-(pbar_0[ii]+qbar_0[ii])/h_0[ii]/(1-pbar_0[ii]*qbar_0[ii]/pow(h_0[ii],2))
tm_1[ii] = tan( h_0[ii]*t_slot )-(pbar_0[ii]+qbar_0[ii])/h_0[ii]/(1-pbar_0[ii]*qbar_0[ii]/pow(h_0[ii],2))

abs_tm_0 = [abs(x) for x in tm_0]
abs_tm_1 = [abs(x) for x in tm_1]
index_TM_0 = abs_tm_0.index(min(abs_tm_0))
index_TM_1 = abs_tm_1.index(min(abs_tm_1))
nTM_0 = b_0[index_TM_0]/k_0
nTM_1 = b_0[index_TM_1]/k_0

while (nTM_0<1.5 or nTM_0>3):
abs_tm_0[index_TM_0] = 100
index_TM_0 = abs_tm_0.index(min(abs_tm_0))
nTM_0 = b_0[index_TM_0]/k_0

while (nTM_1<1.5 or nTM_1>3):
abs_tm_1[index_TM_1] = 100
index_TM_1 = abs_tm_1.index(min(abs_tm_1))
nTM_1 = b_0[index_TM_1]/k_0

ne=dc*nTM_0+(1-dc)*nTM_1

else:
print('Please type TE or TM for polarization')

return ne
#####################################

from math import pi, cos, sin, log, sqrt, tan
from SiEPIC.utils import points_per_circle

lambda_0 = self.wavelength ##um wavelength of light

n_e = effective_index()
#n_e = 1.6975992
ne_fiber = 1 # effective index of the mode in the air
period = self.wavelength/(n_e - sin(pi/180*self.theta_c)*ne_fiber)
period = self.pitch


# Geometry
wh = period*self.dc ##thick grating

gc_number = int(round(self.grating_length/period)) ##number of periods
e = self.n_t*sin((pi/180)*self.theta_c)/n_e
N = round(self.taper_length*(1+e)*n_e/lambda_0) ##allows room for the taper

start = (pi - (pi/180)*self.angle_e/2)
stop = (pi + (pi/180)*self.angle_e/2)

radius_1 = 999 # random number so we know there is an error if radius_1 is not overwritten
radius_2 = 999
# Draw coupler grating.
for j in range(gc_number):

# number of points in the arcs:
# calculate such that the vertex & edge placement error is < 0.5 nm.
# see "SiEPIC_EBeam_functions - points_per_circle" for more details
radius = N*lambda_0 / (n_e*( 1 - e )) + j*period

if j == 0:
radius_1 = radius

if j == 1:
radius_2 = radius

seg_points = int(points_per_circle(radius/dbu, dbu=dbu)/360.*self.angle_e) # number of points grating arc
theta_up = []
for m in range(seg_points+1):
theta_up = theta_up + [start + m*(stop-start)/seg_points]
theta_down = theta_up[::-1]

##big one
r_up = []
r_down = []
for k in range(len(theta_up)):
r_up = r_up + [N*lambda_0 / (n_e*( 1 - e*cos(float(theta_up[k])) )) + j*period+period*(1-self.dc)]
r_down = r_up[::-1]

xr = []
yr = []
for k in range(len(theta_up)):
xr = xr + [r_up[k]*cos(theta_up[k])]
yr = yr + [r_up[k]*sin(theta_up[k])]

xl = []
yl = []
for k in range(len(theta_down)):
xl = xl + [(r_down[k] + wh)*cos(theta_down[k])]
yl = yl + [(r_down[k] + wh)*sin(theta_down[k])]

x = xr + xl
y = yr + yl

pts = []
for i in range(len(x)):
pts.append(Point.from_dpoint(DPoint(x[i]/dbu, y[i]/dbu)))

polygon = Polygon(pts)
shapes(LayerSiN).insert(polygon)

# Taper section
r_up = []
r_down = []
for k in range(len(theta_up)):
r_up = r_up + [N*lambda_0 / (n_e*( 1 - e*cos(float(theta_up[k])) ))]
r_down = r_up[::-1]

xl = []
yl = []
for k in range(len(theta_down)):
xl = xl + [(r_down[k])*cos(theta_down[k])]
yl = yl + [(r_down[k])*sin(theta_down[k])]

yr = [self.t/2., -self.t/2.]

yl_abs = []
for k in range(len(yl)):
yl_abs = yl_abs + [abs(yl[k])]

y_max = max(yl_abs)
iy_max = yl_abs.index(y_max)

L_o = (y_max - self.t/2)/tan((pi/180)*self.angle_e/2)

xr = [0, 0]

x = xr + xl
y = yr + yl

pts = []
for i in range(len(x)):
pts.append(Point.from_dpoint(DPoint(x[i]/dbu, y[i]/dbu)))

polygon = Polygon(pts)
shapes(LayerSiN).insert(polygon)


# Pin on the waveguide:
from SiEPIC._globals import PIN_LENGTH as pin_length
x = 0
t = Trans(Trans.R0, x,0)
pin = Path([Point(-pin_length/2,0), Point(pin_length/2,0)], self.t/dbu)
pin_t = pin.transformed(t)
shapes(LayerPinRecN).insert(pin_t)
text = Text ("pin1", t)
shape = shapes(LayerPinRecN).insert(text)
shape.text_size = 0.4/dbu

# Reference information
t = Trans(Trans.R0, 0,-4000)
text = Text ("Ref: 'Universal grating coupler design'\nhttps://doi.org/10.1117/12.2042185\nPCell implementation by: Yun Wang, Timothy Richards, Adam DeAbreu,\nJonas Flueckiger, Charlie Lin, Lukas Chrostowski, Connor Mosquera\n Adapted by Hang Bobby Zou", t)
shape = shapes(LayerPinRecN).insert(text)
shape.text_size = 0.5/dbu
shape.text_halign=2 # right alignment

t = Trans(Trans.R0, 0,4000)
text = Text ("Wavelength: %s\nIncident Angle: %s\nPolarization: %s\nSiN thickness: %s\nSiN etch depth: %s\nDuty Cycle: %s\nPitch: %s\nRadius_1: %s\nRadius_2: %s\nn_eff: %s\ne: %s\nN: %s\n" % (self.wavelength, self.theta_c, self.pol, self.Si_thickness, self.etch_depth, self.dc, self.pitch, radius_1, radius_2,n_e,e,N), t)
shape = shapes(LayerPinRecN).insert(text)
shape.text_size = 0.5/dbu
shape.text_halign=2 # right alignment
shape.text_valign=2 # bottom alignment

# Device recognition layer
yr = sin(start) * (N*lambda_0 / ( n_e*( 1 - e*cos(float(start)) )) + gc_number*period )
box1 = Box(-(self.grating_length+self.taper_length)/dbu-pin_length*2, yr/dbu, 0, -yr/dbu)
shapes(LayerDevRecN).insert(box1)
Loading

0 comments on commit 88b18a1

Please sign in to comment.