Skip to content

Commit

Permalink
Merge branch 'pr/298'
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasc-ubc committed Sep 10, 2024
2 parents 3266891 + 1e5c27c commit ec041d3
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 28 deletions.
Binary file not shown.
Binary file not shown.
220 changes: 220 additions & 0 deletions klayout/EBeam/pymacros/pcells_EBeam_Beta/spiral_SiN.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import pya
from SiEPIC.utils import get_technology_by_name
from pya import *

class spiral_SiN(pya.PCellDeclarationHelper):
"""
Input:
"""

def __init__(self):

# Important: initialize the super class
super(spiral_SiN, self).__init__()
TECHNOLOGY = get_technology_by_name('EBeam')

# declare the parameters
self.param("length", self.TypeDouble, "Target Waveguide length", default = 500)
self.param("wg_width", self.TypeDouble, "Waveguide width (microns)", default = 0.75)
self.param("min_radius", self.TypeDouble, "Minimum radius (microns)", default = 60)
self.param("wg_spacing", self.TypeDouble, "Waveguide spacing (microns)", default = 1)
self.param("spiral_ports", self.TypeInt, "Ports on the same side? 0/1", default = 0)
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 "spiral_SiN_%s-%.3f-%.3f-%.3f" % \
(self.length, self.wg_width, self.min_radius, self.wg_spacing)

def coerce_parameters_impl(self):
pass

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

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

LayerSi = self.layer
LayerSiN = ly.layer(LayerSi)
LayerPinRecN = ly.layer(self.pinrec)
LayerDevRecN = ly.layer(self.devrec)

# draw spiral
from math import pi, cos, sin, log, sqrt

# Archimedes spiral
# r = b + a * theta
b = self.min_radius
spacing = self.wg_spacing+self.wg_width
a = 2*spacing/(2*pi)

# area, length, turn tracking for spiral
area = 0
spiral_length = 0
turn = -1

from SiEPIC.utils import points_per_circle, arc_wg_xy

while spiral_length < self.length:
turn +=1

# Spiral #1
pts = []
# local radius:
r = 2*b + a * turn * 2 * pi - self.wg_width/2
# number of points per circle:
npoints = int(points_per_circle(r))
# increment, in radians, for each point:
da = 2 * pi / npoints
# draw the inside edge of spiral
for i in range(0, npoints+1):
t = i*da
xa = (a*t + r) * cos(t)
ya = (a*t + r) * sin(t)
pts.append(Point.from_dpoint(DPoint(xa/dbu, ya/dbu)))
# draw the outside edge of spiral
r = 2*b + a * turn * 2 * pi + self.wg_width/2
npoints = int(points_per_circle(r))
da = 2 * pi / npoints
for i in range(npoints, -1, -1):
t = i*da
xa = (a*t + r) * cos(t)
ya = (a*t + r) * sin(t)
pts.append(Point.from_dpoint(DPoint(xa/dbu, ya/dbu)))
polygon = Polygon(pts)
area += polygon.area()
shapes(LayerSiN).insert(polygon)

# Spiral #2
pts = []
# local radius:
r = 2*b + a * turn * 2 * pi - self.wg_width/2 - spacing
# number of points per circle:
npoints = int(points_per_circle(r))
# increment, in radians, for each point:
da = 2 * pi / npoints
# draw the inside edge of spiral
for i in range(0, npoints+1):
t = i*da + pi
xa = (a*t + r) * cos(t)
ya = (a*t + r) * sin(t)
pts.append(Point.from_dpoint(DPoint(xa/dbu, ya/dbu)))
# draw the outside edge of spiral
r = 2*b + a * turn * 2 * pi + self.wg_width/2 - spacing
npoints = int(points_per_circle(r))
da = 2 * pi / npoints
for i in range(npoints, -1, -1):
t = i*da + pi
xa = (a*t + r) * cos(t)
ya = (a*t + r) * sin(t)
pts.append(Point.from_dpoint(DPoint(xa/dbu, ya/dbu)))
polygon = Polygon(pts)
area += polygon.area()
shapes(LayerSiN).insert(polygon)

# waveguide length:
spiral_length = area / self.wg_width * dbu*dbu + 2 * pi * self.min_radius

if self.spiral_ports:
# Spiral #1 extra 1/2 arm
turn = turn + 1
pts = []
# local radius:
r = 2*b + a * turn * 2 * pi - self.wg_width/2
# number of points per circle:
npoints = int(points_per_circle(r))
# increment, in radians, for each point:
da = pi / npoints
# draw the inside edge of spiral
for i in range(0, npoints+1):
t = i*da
xa = (a*t + r) * cos(t)
ya = (a*t + r) * sin(t)
pts.append(Point.from_dpoint(DPoint(xa/dbu, ya/dbu)))
# draw the outside edge of spiral
r = 2*b + a * turn * 2 * pi + self.wg_width/2
npoints = int(points_per_circle(r))
da = pi / npoints
for i in range(npoints, -1, -1):
t = i*da
xa = (a*t + r) * cos(t)
ya = (a*t + r) * sin(t)
pts.append(Point.from_dpoint(DPoint(xa/dbu, ya/dbu)))
polygon = Polygon(pts)
area += polygon.area()
shapes(LayerSiN).insert(polygon)
turn = turn - 1
# waveguide length:
spiral_length = area / self.wg_width * dbu*dbu + 2 * pi * self.min_radius

# Centre S-shape connecting waveguide
#layout_arc_wg_dbu(self.cell, LayerSiN, -b/dbu, 0, b/dbu, self.wg_width/dbu, 0, 180)
self.cell.shapes(LayerSiN).insert(arc_wg_xy(-b/dbu, 0, b/dbu, self.wg_width/dbu, 0, 180))
#layout_arc_wg_dbu(self.cell, LayerSiN, b/dbu, 0, b/dbu, self.wg_width/dbu, 180, 0)
self.cell.shapes(LayerSiN).insert(arc_wg_xy(b/dbu, 0, b/dbu, self.wg_width/dbu, 180, 0))
if debug:
print("spiral length: %s microns" % spiral_length)

# Pins on the waveguide:
from SiEPIC._globals import PIN_LENGTH as pin_length

x = -(2*b + a * (turn+1) * 2 * pi)/dbu
w = self.wg_width / dbu
t = Trans(Trans.R0, x,0)
pin = Path([Point(0,pin_length/2), Point(0,-pin_length/2)], w)
pin_t = pin.transformed(t)
shapes(LayerPinRecN).insert(pin_t)
text = Text ("pin2", t)
shape = shapes(LayerPinRecN).insert(text)
shape.text_size = 0.4/dbu

if self.spiral_ports:
x = -(2*b + a * (turn+1.5) * 2 * pi)/dbu
else:
x = (2*b + a * (turn+1) * 2 * pi)/dbu
t = Trans(Trans.R0, x,0)
pin = Path([Point(0,-pin_length/2), Point(0,pin_length/2)], w)
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

# Compact model information
t = Trans(Trans.R0, -abs(x), 0)
text = Text ('Length=%.3fu' % spiral_length, t)
shape = shapes(LayerDevRecN).insert(text)
shape.text_size = abs(x)/8
t = Trans(Trans.R0, 0, 0)
text = Text ('Lumerical_INTERCONNECT_library=Design kits/ebeam_v1.2', t)
shape = shapes(LayerDevRecN).insert(text)
shape.text_size = 0.1/dbu
t = Trans(Trans.R0, 0, w*2)
text = Text ('Component=ebeam_wg_strip_1550', t)
shape = shapes(LayerDevRecN).insert(text)
shape.text_size = 0.1/dbu
t = Trans(Trans.R0, 0, -w*2)
text = Text \
('Spice_param:wg_length=%.3fu wg_width=%.3fu min_radius=%.3fu wg_spacing=%.3fu' %\
(spiral_length, self.wg_width, (self.min_radius), self.wg_spacing), t )
shape = shapes(LayerDevRecN).insert(text)
shape.text_size = 0.1/dbu

# Create the device recognition layer -- make it 1 * wg_width away from the waveguides.
x = abs(x)
npoints = int(points_per_circle(x) /10 )
da = 2 * pi / npoints # increment, in radians
r=x + 2 * self.wg_width/dbu
pts = []
for i in range(0, npoints+1):
pts.append(Point.from_dpoint(DPoint(r*cos(i*da), r*sin(i*da))))
shapes(LayerDevRecN).insert(Polygon(pts))

# print("spiral done.")
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,42 @@ 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):
class ebeam_GC_SiN_TE_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)
This design is based on the Universal Grating Coupler PCell implementation. Which details can be found below:
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)
This design was adapted by: Hang (Bobby) Zou from Si to SiN platform, with default parameters set as best device parameters with real fabrication run
Date Edited: 2024-08-28
"""

def __init__(self):

# Important: initialize the super class
super(EBeam_GC_SiN_1310_8deg, self).__init__()
super(ebeam_GC_SiN_TE_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("Si_thickness", self.TypeDouble, "SiN Thickness (micron)", default = 0.35)
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.4223)
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.5)
self.param('pitch', self.TypeDouble, "Pitch (micron)", default = 1)
self.param("dc", self.TypeDouble, "Duty Cylce", default = 0.55)
self.param('pitch', self.TypeDouble, "Pitch (micron)", default = 0.94)
self.param("t", self.TypeDouble, "Waveguide Width (micron)", default = 0.75)
self.param("theta_c", self.TypeDouble, "Insertion Angle (deg)", default = 8.0)
self.param("theta_c", self.TypeDouble, "Insertion Angle (deg)", default = 8.121)

# Layer parameters
self.param("layer", self.TypeLayer, "Layer", default = TECHNOLOGY['SiN'])
Expand All @@ -45,11 +49,9 @@ def __init__(self):

def display_text_impl(self):
# Provide a descriptive text for the cell
return "Universal_GC_%.1f-%.2f-%.2f-%.2f-%.2f-%.2f" % \
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 "temporary placeholder"

def coerce_parameters_impl(self):
pass

Expand Down Expand Up @@ -78,13 +80,13 @@ def effective_index(wl = self.wavelength, etch_depth = self.etch_depth, Si_thick
from SiEPIC.utils import points_per_circle

point =1001
n_0 = n_t
n_0 = n_t # Top cladding
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
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
Expand Down Expand Up @@ -182,6 +184,7 @@ def effective_index(wl = self.wavelength, etch_depth = self.etch_depth, Si_thick
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
Expand All @@ -190,20 +193,29 @@ def effective_index(wl = self.wavelength, etch_depth = self.etch_depth, Si_thick
# 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
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):
Expand Down Expand Up @@ -291,13 +303,13 @@ def effective_index(wl = self.wavelength, etch_depth = self.etch_depth, Si_thick
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_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\nSilicon thickness: %s\nSilicon etch depth: %s\nRadius: %s" % (self.wavelength, self.theta_c, self.pol, self.Si_thickness, self.etch_depth, radius), t)
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.4/dbu
shape.text_size = 0.5/dbu
shape.text_halign=2 # right alignment
shape.text_valign=2 # bottom alignment

Expand Down

0 comments on commit ec041d3

Please sign in to comment.