Skip to content

Commit

Permalink
Merge pull request #292 from Hang-Bobby-Zou/master
Browse files Browse the repository at this point in the history
Create EBeam_GC_SiN_1310_8deg.py
  • Loading branch information
lukasc-ubc authored Jul 29, 2024
2 parents 1525cd1 + 8486160 commit ada3345
Showing 1 changed file with 307 additions and 0 deletions.
307 changes: 307 additions & 0 deletions klayout/EBeam/pymacros/pcells_SiN/EBeam_GC_SiN_1310_8deg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
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 EBeam_GC_SiN_1310_8deg(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)
"""

def __init__(self):

# Important: initialize the super class
super(EBeam_GC_SiN_1310_8deg, 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, hidden = True)
self.param("pol", self.TypeString, "Polarization", default = "TE")
self.param("n_t", self.TypeDouble, "Cladding Index", default = 1.4223)
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.5)
self.param('pitch', self.TypeDouble, "Pitch (micron)", default = 1)
self.param("t", self.TypeDouble, "Waveguide Width (micron)", default = 0.75)
self.param("theta_c", self.TypeDouble, "Insertion Angle (deg)", default = 8.0)

# 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 "Universal_GC_%.1f-%.2f-%.2f-%.2f-%.2f-%.2f" % \
(self.wavelength, self.theta_c, self.dc, self.angle_e, self.taper_length, self.t)

# return "temporary placeholder"

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
n_1 = 0
n_3 = 1.4223
#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.0031 # 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()
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 by dividing the defined length and rounding it
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)

# 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
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.4/dbu
shape.text_halign=2 # right alignment

t = Trans(Trans.R0, 0,4000)
text = Text ("Wavelength: %s\nIncident Angle: %s\nPolarization: %s\nSilicon thickness: %s\nSilicon etch depth: %s\nRadius: %s" % (self.wavelength, self.theta_c, self.pol, self.Si_thickness, self.etch_depth, radius), t)
shape = shapes(LayerPinRecN).insert(text)
shape.text_size = 0.4/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)

0 comments on commit ada3345

Please sign in to comment.