-
Notifications
You must be signed in to change notification settings - Fork 150
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added support for GC, and a spiral for SiN components in the beta Lib…
…rary
- Loading branch information
1 parent
ada3345
commit 88b18a1
Showing
2 changed files
with
539 additions
and
0 deletions.
There are no files selected for viewing
319 changes: 319 additions & 0 deletions
319
klayout/EBeam/pymacros/pcells_EBeam_Beta/AA_EBeam_GC_SiN_1310_12deg.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.