1318 lines
58 KiB
Python
1318 lines
58 KiB
Python
from typing import Any, Optional
|
|
import nazca as nd
|
|
import numpy as np
|
|
import math
|
|
|
|
from ...geometry import *
|
|
from ...geometry import _my_polygon
|
|
from ...basic import __cell_arg__
|
|
from ...routing import Route
|
|
|
|
import pandas as pd
|
|
|
|
''' Class for nanoantenna '''
|
|
class Nano_ant():
|
|
|
|
"""
|
|
Class of nanoantenna for optical phased array.
|
|
|
|
This is the class of nanoantenna for optical phased array. GDS cell can be generated using this class. Simulation structure generation and simulation results analysis is going to be added in the future.
|
|
|
|
Parameters
|
|
----------
|
|
w_wg : float, optional
|
|
Width parameter in microns. Default is 0.41.
|
|
xs_wg : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
define_type : str, optional
|
|
Value for the define_type parameter. Default is 'non-periodic'.
|
|
vector : list, optional
|
|
Value for the vector parameter. Default is [0.5, 0.5, 0.5, 0.5, 0.5, 0.5].
|
|
taper_length : float, optional
|
|
Value for the taper_length parameter. Default is 3.
|
|
width : float, optional
|
|
Width parameter in microns. Default is 6.
|
|
max_theta : float, optional
|
|
Value for the max_theta parameter. Default is 110.
|
|
pitch : float, optional
|
|
Spacing or gap parameter in microns. Default is 0.6.
|
|
duty_cycle : float, optional
|
|
Value for the duty_cycle parameter. Default is 0.3.
|
|
teeth_number : float, optional
|
|
Value for the teeth_number parameter. Default is 6.
|
|
etch_depth : list, optional
|
|
Value for the etch_depth parameter. Default is ['METCH'].
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is True.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
w_wg: float = 0.41,
|
|
xs_wg: str = "strip",
|
|
|
|
define_type: str = "non-periodic",
|
|
vector: list = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
|
|
taper_length: float = 3,
|
|
width: float = 6,
|
|
max_theta: float = 110,
|
|
pitch: float = 0.6,
|
|
duty_cycle: float = 0.3,
|
|
teeth_number: float = 6,
|
|
|
|
etch_depth: list = ["METCH"],
|
|
show_pins: bool = True
|
|
) -> None:
|
|
# Init and save the input parameters
|
|
self.w_wg = w_wg
|
|
self.xs_wg = xs_wg
|
|
# Determine the etch type
|
|
if len(etch_depth)==1: self.etch_type = "single"
|
|
elif len(etch_depth)==2: self.etch_type = "dual"
|
|
if define_type=="non-periodic":
|
|
self.vector = vector
|
|
self.teeth_number = len(vector)/2
|
|
elif define_type=="periodic":
|
|
# Parameters necessary when the ant is defined by "periodic" way
|
|
if self.etch_type=="single":
|
|
self.vector = [pitch*((2*duty_cycle-1)*(index%2)+1-duty_cycle) for index in range(teeth_number*2)]
|
|
self.pitch = pitch
|
|
self.duty_cycle = duty_cycle
|
|
self.teeth_number = teeth_number
|
|
elif self.etch_type=="dual":
|
|
self.vector = [
|
|
pitch[1]*((2*duty_cycle[1]-1)*(index%2)+1-duty_cycle[1]) for index in range(teeth_number*2)
|
|
]
|
|
self.vector[0] = pitch[0]*(1-duty_cycle[0])
|
|
self.vector[1] = pitch[0]*duty_cycle[0]
|
|
self.pitch = pitch
|
|
self.duty_cycle = duty_cycle
|
|
self.teeth_number = teeth_number
|
|
|
|
self.taper_length = taper_length
|
|
self.ant_length = self.taper_length + sum(self.vector)
|
|
self.width = width
|
|
self.max_theta = max_theta
|
|
self.define_type = define_type
|
|
# Here, I should change the name-type according to the difinition in the foundry.py
|
|
self.etch_depth = []
|
|
for etch in etch_depth:
|
|
if etch=="FETCH": self.etch_depth = self.etch_depth+["STRIP"] # self.etch_depth.append("STRIP")
|
|
elif etch=="METCH": self.etch_depth = self.etch_depth+["RIB"] # self.etch_depth.append("RIB")
|
|
elif etch=="SETCH": self.etch_depth = self.etch_depth+["SRIB"] # self.etch_depth.append("SRIB")
|
|
|
|
self.show_pins = show_pins
|
|
|
|
self.cell = self.generate_gds()
|
|
|
|
def generate_gds(self, sample_step=0.1, cell_name="Nanoantenna"):
|
|
with nd.Cell(name=cell_name, instantiate=False) as nano_ant:
|
|
layer_tre = nd.get_layer("STRIP_TRE")
|
|
if layer_tre == "STRIP_TRE" :
|
|
self.generate_gds_positive(sample_step=sample_step)
|
|
else : ## TO DO
|
|
self.generate_gds_error()
|
|
# Add pins
|
|
nd.Pin(name="a0", width=self.w_wg).put(0, 0, 180)
|
|
nd.Pin(name="g1", width=self.w_wg).put(0, 0, 180)
|
|
if self.show_pins:
|
|
nd.put_stub(pinname="g1")
|
|
return nano_ant
|
|
|
|
def generate_gds_positive(self, sample_step=0.1):
|
|
"""
|
|
Generate a gds cell based on the logic of positive photoresistance.
|
|
|
|
| Positive: Define the etched region using GETCH_TRE layer.
|
|
"""
|
|
width_extra_trench = 0.1
|
|
theta_rad_max = self.max_theta * math.pi / 180
|
|
## Check if the input is appropriate or not
|
|
if math.floor(self.teeth_number) != self.teeth_number:
|
|
print("WARNNING :: Please re-check the vector of your antenna and make sure the length of vector is even.")
|
|
message = 'Inappropriate Definition of antenna.'
|
|
nd.text(text=message, height=5, layer=(96, 0), align='cc').put(0, 0)
|
|
return 0
|
|
## Build the structure
|
|
# Add input waveguide
|
|
nd.strt(length=self.taper_length-0.5, width=self.w_wg, xs=self.xs_wg).put(0, 0)
|
|
# Add the fan polygon region
|
|
radius_max = self.taper_length + sum(self.vector)
|
|
if self.width/2 > radius_max: theta_rad = theta_rad_max/2
|
|
elif math.asin(self.width/2/radius_max) > theta_rad_max/2: theta_rad = theta_rad_max/2
|
|
elif math.asin(self.width/2/radius_max) <= theta_rad_max/2: theta_rad = math.asin(self.width/2/radius_max)
|
|
theta_list = np.linspace(-theta_rad, theta_rad, math.floor(theta_rad*2*radius_max/sample_step))
|
|
fan_polygon = [(radius_max*math.cos(theta), radius_max*math.sin(theta)) for theta in theta_list]
|
|
# fan_polygon = fan_polygon + [(self.width/2/math.tan(theta_rad_max/2), self.width/2), (0, self.w_wg/2),
|
|
# (0, -self.w_wg/2), (self.width/2/math.tan(theta_rad_max/2), -self.width/2)]
|
|
fan_polygon = fan_polygon + [(self.width/2/math.tan(theta_rad_max/2), self.width/2), (0, 0),
|
|
(self.width/2/math.tan(theta_rad_max/2), -self.width/2)]
|
|
nd.Polygon(points=fan_polygon, layer="STRIP_COR").put(0, 0)
|
|
layer_cld = nd.get_layer("STRIP_CLD")
|
|
if layer_cld == "STRIP_CLD": # Add CLD region if necessary
|
|
nd.strt(length=self.ant_length+1, width=max(self.width+1, self.w_wg+4),
|
|
layer=layer_cld).put(0, 0)
|
|
# Add the teeth
|
|
radius_cur = self.taper_length
|
|
for teeth_index in range(0, int(self.teeth_number)):
|
|
## Determine the angular region first
|
|
if teeth_index == 0: radius_ref = radius_cur
|
|
else: radius_ref = radius_cur + self.vector[teeth_index*2-1]
|
|
|
|
if (self.width+width_extra_trench)/2 > radius_ref:
|
|
theta_rad = theta_rad_max/2 + width_extra_trench/radius_ref
|
|
elif math.asin((self.width+width_extra_trench)/2/radius_ref) > theta_rad_max/2:
|
|
theta_rad = theta_rad_max/2 + width_extra_trench/radius_ref
|
|
else:
|
|
theta_rad = math.asin((self.width+width_extra_trench)/2/radius_ref)
|
|
|
|
theta_step = sample_step / radius_ref
|
|
theta_list = np.linspace(-theta_rad, theta_rad, math.floor(2*theta_rad/theta_step))
|
|
## Construct the inner radius curve
|
|
if teeth_index == 0: radius_cur = radius_cur
|
|
else: radius_cur = radius_cur + self.vector[teeth_index*2-1]
|
|
inner_radius_curve = [(radius_cur*math.cos(theta), radius_cur*math.sin(theta)) for theta in theta_list]
|
|
## Construct the outer radius curve
|
|
radius_cur = radius_cur + self.vector[teeth_index*2]
|
|
outer_radius_curve = [(radius_cur*math.cos(theta), radius_cur*math.sin(theta)) for theta in theta_list]
|
|
outer_radius_curve.reverse()
|
|
## Add two dummy points to avoid sharp angle
|
|
offset_length = 0.015 / 2
|
|
minimum_etch = 0.2
|
|
radius_inner = radius_cur - self.vector[teeth_index*2]
|
|
radius_outer = radius_inner + self.vector[teeth_index*2]
|
|
x_1 = radius_inner * math.cos(theta_rad) + offset_length * math.cos(theta_rad)
|
|
y_1 = radius_inner * math.sin(theta_rad) + offset_length * math.sin(theta_rad)
|
|
vertical_length = math.sqrt(np.power(minimum_etch, 2) - np.power(offset_length, 2))
|
|
dummy1_x = x_1 - vertical_length * math.sin(theta_rad)
|
|
dummy1_y = y_1 + vertical_length * math.cos(theta_rad)
|
|
x_2 = radius_outer * math.cos(theta_rad) - offset_length * math.cos(theta_rad)
|
|
y_2 = radius_outer * math.sin(theta_rad) - offset_length * math.sin(theta_rad)
|
|
dummy2_x = x_2 - vertical_length * math.sin(theta_rad)
|
|
dummy2_y = y_2 + vertical_length * math.cos(theta_rad)
|
|
## Construct the teeth polygon
|
|
teeth_polygon = inner_radius_curve+[(dummy1_x,dummy1_y),(dummy2_x,dummy2_y)]+outer_radius_curve+[(dummy2_x, -dummy2_y),(dummy1_x,-dummy1_y)]
|
|
if self.etch_type == "single":
|
|
nd.Polygon(points=teeth_polygon, layer=self.etch_depth[0]+"_TRE").put(0, 0)
|
|
if self.etch_type == "dual":
|
|
if teeth_index==0: nd.Polygon(points=teeth_polygon, layer=self.etch_depth[0]+"_TRE").put(0, 0)
|
|
else: nd.Polygon(points=teeth_polygon, layer=self.etch_depth[1]+"_TRE").put(0, 0)
|
|
|
|
def generate_gds_error(self):
|
|
nd.text(text="This foundry is not compatiable with current device. Please check.", height=10, layer=1001).put(0, 0)
|
|
|
|
''' Class for 2D antenna array for FMF grating '''
|
|
class Taper() :
|
|
|
|
"""
|
|
Taper primitive component.
|
|
|
|
This component builds the Taper layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
width1 : float, optional
|
|
Width parameter in microns. Default is 4.
|
|
width2 : float, optional
|
|
Width parameter in microns. Default is 0.45.
|
|
length : float, optional
|
|
Length parameter in microns. Default is 30.
|
|
type : str, optional
|
|
Value for the type parameter. Default is 'linear'.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
"""
|
|
def __init__(self, width1: float=4, width2: float=0.45, length: float=30, type: str="linear", show_pins: bool=False) -> None:
|
|
self.width1 = width1
|
|
self.width2 = width2
|
|
self.length = length
|
|
self.type = type
|
|
|
|
if self.type == "parabolic" : self.order = 2
|
|
elif self.type == "linear" : self.order = 1
|
|
self.show_pins = show_pins
|
|
|
|
self.cell = self.generate_gds()
|
|
|
|
def generate_gds(self) :
|
|
with nd.Cell(name="taper", instantiate=False) as ic :
|
|
if self.order == 1 :
|
|
strip = Route(radius=10,width=self.width1,xs='strip')
|
|
linear_taper = strip.taper(
|
|
length=self.length,width1=self.width1,width2=self.width2,patch=True).put(0,0,0)
|
|
output_strt = strip.strt(length=0.5,width=self.width2).put()
|
|
nd.Pin(name="a1",width=self.width1).put(linear_taper.pin['a0'])
|
|
nd.Pin(name="b1",width=self.width2).put(output_strt.pin['b0'])
|
|
else :
|
|
c2 = self.width1/2
|
|
c1 = (c2 - self.width2/2) / np.power(self.length, self.order)
|
|
x_list = np.linspace(0, self.length, int(np.floor(self.length/0.2)))
|
|
|
|
taper_up_poly = [(x, -c1*np.power(x, self.order)+c2) for x in x_list]
|
|
taper_down_poly = [(x, -(-c1*np.power(x, self.order)+c2)) for x in x_list]
|
|
taper_down_poly.reverse()
|
|
taper_poly = taper_up_poly + taper_down_poly
|
|
nd.Polygon(points=taper_poly, layer='STRIP_COR').put(0,0)
|
|
|
|
c2 = (self.width1+4)/2
|
|
c1 = (c2 - (self.width2+4)/2) / np.power(self.length, self.order)
|
|
x_list = np.linspace(0, self.length, int(np.floor(self.length/0.2)))
|
|
|
|
taper_up_poly = [(x, -c1*np.power(x, self.order)+c2) for x in x_list]
|
|
taper_down_poly = [(x, -(-c1*np.power(x, self.order)+c2)) for x in x_list]
|
|
taper_down_poly.reverse()
|
|
taper_poly = taper_up_poly + taper_down_poly
|
|
nd.Polygon(points=taper_poly, layer='STRIP_CLD').put(0,0)
|
|
|
|
width_max = np.max(np.array([self.width1, self.width2]))
|
|
taper_poly = [
|
|
(0, width_max/2+2), (0, -width_max/2-2),
|
|
(self.length, -width_max/2-2), (self.length, width_max/2+2)
|
|
]
|
|
nd.Polygon(points=taper_poly, layer='STRIP_CLD').put(0,0)
|
|
|
|
nd.strt(length=0.5, width=self.width2, xs='strip').put(self.length,0,0)
|
|
|
|
nd.Pin(name='a1',width=self.width1).put(0,0,180)
|
|
nd.Pin(name="b1",width=self.width2).put(self.length+0.5,0,0)
|
|
if self.show_pins :
|
|
nd.put_stub()
|
|
return ic
|
|
|
|
|
|
class Grating_2D_Hole() :
|
|
"""
|
|
This is a class for 2D Grating in IMEC.
|
|
|
|
Parameters
|
|
----------
|
|
w_wg : float, optional
|
|
Width parameter in microns. Default is 0.5.
|
|
w_gt : float, optional
|
|
Width parameter in microns. Default is 5.
|
|
l_taper : float, optional
|
|
Value for the l_taper parameter. Default is 30.
|
|
type_taper : str, optional
|
|
Value for the type_taper parameter. Default is 'parabolic'.
|
|
gt_vector : list, optional
|
|
Value for the gt_vector parameter. Default is [0.5, 0.5, 0.5, 0.5, 0.5].
|
|
gt_diameter : float, optional
|
|
Value for the gt_diameter parameter. Default is 0.4.
|
|
gt_layer : str, optional
|
|
Value for the gt_layer parameter. Default is 'STRIP_COR'.
|
|
polysi_vector : list, optional
|
|
Value for the polysi_vector parameter. Default is [0.5, 0.5, 0.5, 0.5, 0.5].
|
|
polysi_diameter : float, optional
|
|
Value for the polysi_diameter parameter. Default is 0.4.
|
|
polysi_layer : str, optional
|
|
Value for the polysi_layer parameter. Default is 'FCW_TRE'.
|
|
reflector_vector : list, optional
|
|
Value for the reflector_vector parameter. Default is [0.3, 0.3, 0.3, 0.3, 0.3, 0.3].
|
|
l_field_center : float, optional
|
|
Value for the l_field_center parameter. Default is 1.
|
|
"""
|
|
def __init__(
|
|
self,
|
|
w_wg: float=0.5,
|
|
w_gt: float=5, l_taper: float=30, type_taper: str="parabolic",
|
|
gt_vector: list=[0.5,0.5,0.5,0.5,0.5,], gt_diameter: float=0.4, gt_layer: str="STRIP_COR",
|
|
polysi_vector: list=[0.5,0.5,0.5,0.5,0.5], polysi_diameter: float=0.4, polysi_layer: str="FCW_TRE",
|
|
reflector_vector: list=[0.3,0.3,0.3,0.3,0.3,0.3],
|
|
l_field_center: float = 1
|
|
) -> None:
|
|
self.w_wg = w_wg
|
|
self.w_gt = w_gt
|
|
self.l_taper = l_taper
|
|
self.type_taper = type_taper
|
|
|
|
self.gt_vector = gt_vector
|
|
self.gt_num = len(self.gt_vector)
|
|
self.gt_diameter = gt_diameter
|
|
self.gt_layer =gt_layer
|
|
|
|
self.polysi_vector = polysi_vector
|
|
self.polysi_num = len(self.polysi_vector)
|
|
self.polysi_diameter = polysi_diameter
|
|
self.polysi_layer = polysi_layer
|
|
|
|
self.reflector_vector = reflector_vector
|
|
|
|
self.l_field_center = l_field_center
|
|
|
|
self.cell = self.generate_gds()
|
|
|
|
def generate_gds(self) :
|
|
with nd.Cell(name="2D_Grating", instantiate=False) as ic :
|
|
'''Generate the diffraction region first.'''
|
|
strip_cor_poly = [
|
|
(self.w_gt/2, self.w_gt/2), (self.w_gt/2, -self.w_gt/2),
|
|
(-self.w_gt/2, -self.w_gt/2), (-self.w_gt/2, self.w_gt/2)
|
|
]
|
|
nd.Polygon(points=strip_cor_poly, layer='STRIP_COR').put(0,0,0)
|
|
strip_cld_poly = [
|
|
(self.w_gt/2+2, self.w_gt/2+2), (self.w_gt/2+2, -self.w_gt/2-2),
|
|
(-self.w_gt/2-2, -self.w_gt/2-2), (-self.w_gt/2-2, self.w_gt/2+2)
|
|
]
|
|
nd.Polygon(points=strip_cld_poly, layer='STRIP_CLD').put(0,0,0)
|
|
'''Generate the reflection region.'''
|
|
self.reflector_num = int(len(self.reflector_vector)/2)
|
|
for index in range(self.reflector_num) :
|
|
loc = self.w_gt/2 + sum(self.reflector_vector[0:2*index+1]) + self.reflector_vector[2*index+1]/2
|
|
nd.strt(length=self.w_gt, width=self.reflector_vector[2*index+1], xs='strip').put(
|
|
-loc, -(self.w_gt)/2, 90
|
|
)
|
|
nd.strt(length=self.w_gt, width=self.reflector_vector[2*index+1], xs='strip').put(
|
|
-(self.w_gt)/2, -loc, 0
|
|
)
|
|
'''Generate the taper output.'''
|
|
taper = Taper(width1=self.w_gt, width2=self.w_wg, length=self.l_taper, type=self.type_taper)
|
|
taper_horizontal = taper.cell.put('a1', self.w_gt/2,0,0)
|
|
taper_vertical = taper.cell.put('a1',0,self.w_gt/2,90)
|
|
'''Generate the diffraction etched region.'''
|
|
theta_list = np.linspace(0, 2*np.pi, 32)
|
|
gt_ring_poly = [
|
|
(self.gt_diameter/2*np.cos(theta), self.gt_diameter/2*np.sin(theta)) for theta in theta_list
|
|
]
|
|
polysi_ring_poly = [
|
|
(self.polysi_diameter/2*np.cos(theta), self.polysi_diameter/2*np.sin(theta)) for theta in theta_list
|
|
]
|
|
self._generate_hole_array_(
|
|
polygon=gt_ring_poly, vector=self.gt_vector, layer=self.gt_layer
|
|
).put(self.w_gt/2, self.w_gt/2)
|
|
self._generate_hole_array_(
|
|
polygon=polysi_ring_poly, vector=self.polysi_vector, layer=self.polysi_layer
|
|
).put(self.w_gt/2, self.w_gt/2)
|
|
'''Put the pin location'''
|
|
nd.Pin(name='g1').put(taper_horizontal.pin['b1'])
|
|
nd.Pin(name='g2').put(taper_vertical.pin['b1'])
|
|
|
|
# nd.put_stub()
|
|
return ic
|
|
|
|
def _generate_hole_array_(self,polygon,vector,layer) :
|
|
with nd.Cell(name="diffration_"+layer, instantiate=False) as ic :
|
|
for lateral_index in range(len(vector)) :
|
|
for vertical_index in range(len(vector)) :
|
|
nd.Polygon(points=polygon,layer=layer).put(
|
|
-sum(vector[0:lateral_index+1]), -sum(vector[0:vertical_index+1])
|
|
)
|
|
return ic
|
|
|
|
class Grating_2D_Hole_4Rec() :
|
|
|
|
"""
|
|
Grating 2D Hole 4Rec primitive component.
|
|
|
|
This component builds the Grating 2D Hole 4Rec layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
grating_unit : Any
|
|
Grating unit cell or component used by this wrapper.
|
|
mode_radius : int, optional
|
|
Value for the mode_radius parameter. Default is 8.
|
|
cell_name : Optional[str], optional
|
|
Optional generated cell name. Default is None.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
"""
|
|
def __init__(self, grating_unit: Any, mode_radius: int=8, cell_name: Optional[str]=None, show_pins: bool=False) -> None:
|
|
self.gt_2D_class = grating_unit
|
|
self.cell_unit = grating_unit.cell
|
|
self.mode_radius = mode_radius
|
|
|
|
# Calculate the field center location
|
|
# radius = np.sqrt(2)/2 * (
|
|
# self.mode_radius + np.sqrt(2)/2*(grating_unit.w_gt/2-grating_unit.l_field_center) -
|
|
# np.sqrt(np.power(self.mode_radius, 2) - 1/2*np.power(grating_unit.w_gt/2-grating_unit.l_field_center, 2))
|
|
# )
|
|
# print("---------------------"+str(radius)+"------------------------------")
|
|
l_field_center = grating_unit.l_field_center
|
|
w_gt = grating_unit.w_gt
|
|
x0 = (
|
|
2*(w_gt/2-l_field_center)-np.sqrt(
|
|
8*mode_radius**2 - 4 * (w_gt/2 - l_field_center)**2
|
|
)
|
|
) / 4
|
|
self.field_center = (
|
|
x0 + mode_radius*np.cos(np.pi/4),
|
|
x0 + mode_radius*np.cos(np.pi/4),
|
|
180
|
|
)
|
|
self.cell_unit._put_pin(name='g0', connect=self.field_center)
|
|
|
|
self.show_pins = show_pins
|
|
self.cell_name = cell_name
|
|
self.cell = self.generate_gds()
|
|
|
|
def generate_gds(self) :
|
|
if self.cell_name is not None : self.cell_name = "TwoD_Grating_" + self.cell_name
|
|
else : self.cell_name = "TwoD_Grating"
|
|
with nd.Cell(name=self.cell_name, instantiate=False) as ic :
|
|
gt_1 = self.cell_unit.put(
|
|
'g0', self.mode_radius*np.cos(np.pi/4), self.mode_radius*np.sin(np.pi/4)
|
|
)
|
|
gt_2 = self.cell_unit.put(
|
|
'g0', self.mode_radius*np.cos(np.pi/4), -self.mode_radius*np.sin(np.pi/4), flip=True
|
|
)
|
|
gt_3 = self.cell_unit.put(
|
|
'g0', -self.mode_radius*np.cos(np.pi/4), -self.mode_radius*np.sin(np.pi/4), flip=True, flop=True
|
|
)
|
|
gt_4 = self.cell_unit.put(
|
|
'g0', -self.mode_radius*np.cos(np.pi/4), self.mode_radius*np.sin(np.pi/4), flip=False, flop=True
|
|
)
|
|
'''Put OPEN and PATH region if necessary.'''
|
|
if nd.get_layer(layer="GC_OPEN") == "GC_OPEN" :
|
|
nd.Polygon(
|
|
points=nd.geom.circle(radius=self.mode_radius+20, N=int(np.floor((self.mode_radius+20)/0.1))),
|
|
layer="GC_OPEN"
|
|
).put(0,0)
|
|
if nd.get_layer(layer="STRIP_CLD") == "STRIP_CLD" :
|
|
nd.Polygon(
|
|
points=nd.geom.circle(radius=self.mode_radius+10, N=int(np.floor((self.mode_radius+20)/0.1))),
|
|
layer="STRIP_CLD"
|
|
).put(0,0)
|
|
''' Put Pins '''
|
|
nd.Pin(name='g1').put(gt_1.pin['g1'])
|
|
nd.Pin(name='g2').put(gt_1.pin['g2'])
|
|
nd.Pin(name='g3').put(gt_2.pin['g1'])
|
|
nd.Pin(name='g4').put(gt_2.pin['g2'])
|
|
nd.Pin(name='g5').put(gt_3.pin['g1'])
|
|
nd.Pin(name='g6').put(gt_3.pin['g2'])
|
|
nd.Pin(name='g7').put(gt_4.pin['g1'])
|
|
nd.Pin(name='g8').put(gt_4.pin['g2'])
|
|
nd.Pin(name='a0').put(gt_1.pin['g1'].x, 0, 0)
|
|
if self.show_pins :
|
|
nd.put_stub()
|
|
return ic
|
|
|
|
class Grating_2D_Hole_3Rec() :
|
|
|
|
"""
|
|
Grating 2D Hole 3Rec primitive component.
|
|
|
|
This component builds the Grating 2D Hole 3Rec layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
grating_unit : Any
|
|
Grating unit cell or component used by this wrapper.
|
|
mode_radius : float, optional
|
|
Value for the mode_radius parameter. Default is 6.5.
|
|
cell_name : Optional[str], optional
|
|
Optional generated cell name. Default is None.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
"""
|
|
def __init__(self, grating_unit: Any, mode_radius: float=6.5, cell_name: Optional[str]=None, show_pins: bool=False) -> None:
|
|
self.gt_2D_class = grating_unit
|
|
self.cell_unit = grating_unit.cell
|
|
self.mode_radius = mode_radius
|
|
self.cell_name = cell_name
|
|
|
|
# Calculate the field center location
|
|
radius = np.sqrt(2)/2 * (
|
|
self.mode_radius + np.sqrt(2)/2*(grating_unit.w_gt/2-grating_unit.l_field_center) -
|
|
np.sqrt(np.power(self.mode_radius, 2) - 1/2*np.power(grating_unit.w_gt/2-grating_unit.l_field_center, 2))
|
|
)
|
|
self.field_center = (
|
|
radius*np.cos(np.pi/4),
|
|
radius*np.cos(np.pi/4),
|
|
45
|
|
)
|
|
self.cell_unit._put_pin(name='g0', connect=self.field_center)
|
|
self.show_pins = show_pins
|
|
|
|
self.cell = self.generate_gds()
|
|
|
|
def generate_gds(self) :
|
|
if self.cell_name is not None : self.cell_name = "TwoD_Grating_" + self.cell_name
|
|
else : self.cell_name = "TwoD_Grating"
|
|
with nd.Cell(name=self.cell_name, instantiate=False) as ic :
|
|
rotation_angle = 2*np.pi/3*0
|
|
gt_1 = self.cell_unit.put(
|
|
'g0',
|
|
self.mode_radius*np.cos(rotation_angle), self.mode_radius*np.sin(rotation_angle),
|
|
180 + rotation_angle*180/np.pi
|
|
)
|
|
rotation_angle = 2*np.pi/3*1
|
|
gt_2 = self.cell_unit.put(
|
|
'g0',
|
|
self.mode_radius*np.cos(rotation_angle), self.mode_radius*np.sin(rotation_angle),
|
|
180 + rotation_angle*180/np.pi
|
|
)
|
|
rotation_angle = 2*np.pi/3*2
|
|
gt_3 = self.cell_unit.put(
|
|
'g0',
|
|
self.mode_radius*np.cos(rotation_angle), self.mode_radius*np.sin(rotation_angle),
|
|
180 + rotation_angle*180/np.pi
|
|
)
|
|
'''Put OPEN and PATH region if necessary.'''
|
|
if nd.get_layer(layer="GC_OPEN") == "GC_OPEN" :
|
|
nd.Polygon(
|
|
points=nd.geom.circle(radius=self.mode_radius+20, N=int(np.floor((self.mode_radius+20)/0.1))),
|
|
layer="GC_OPEN"
|
|
).put(0,0)
|
|
if nd.get_layer(layer="STRIP_CLD") == "STRIP_CLD" :
|
|
nd.Polygon(
|
|
points=nd.geom.circle(radius=self.mode_radius+10, N=int(np.floor((self.mode_radius+20)/0.1))),
|
|
layer="STRIP_CLD"
|
|
).put(0,0)
|
|
'''Put pins'''
|
|
nd.Pin(name='g1').put(gt_1.pin['g1'])
|
|
nd.Pin(name='g2').put(gt_1.pin['g2'])
|
|
nd.Pin(name='g3').put(gt_2.pin['g1'])
|
|
nd.Pin(name='g4').put(gt_2.pin['g2'])
|
|
nd.Pin(name='g5').put(gt_3.pin['g1'])
|
|
nd.Pin(name='g6').put(gt_3.pin['g2'])
|
|
if self.show_pins :
|
|
nd.put_stub()
|
|
return ic
|
|
|
|
|
|
""" Renamed for simplification in 2023.04.02 """
|
|
class GC_STD_2D:
|
|
"""
|
|
GC STD 2D primitive component.
|
|
|
|
This component builds the GC STD 2D layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
name : Optional[str], optional
|
|
Unique identifier for the device cell. Default is None.
|
|
etch_type : str, optional
|
|
Value for the etch_type parameter. Default is 'FETCH'.
|
|
xs_wg : str, optional
|
|
Layer or cross-section name used by the device. Default is 'grating'.
|
|
Dx_hole : float, optional
|
|
Value for the Dx_hole parameter. Default is 0.3.
|
|
Dy_hole : float, optional
|
|
Value for the Dy_hole parameter. Default is 0.3.
|
|
hole_shape : str, optional
|
|
Value for the hole_shape parameter. Default is 'circle'.
|
|
shape : str, optional
|
|
Value for the shape parameter. Default is 'circle'.
|
|
xs_open : str, optional
|
|
Layer or cross-section name used by the device. Default is None.
|
|
Px : float, optional
|
|
Value for the Px parameter. Default is 0.57.
|
|
Py : float, optional
|
|
Value for the Py parameter. Default is 0.57.
|
|
num_x : float, optional
|
|
Count or repetition parameter. Default is 25.
|
|
num_y : float, optional
|
|
Count or repetition parameter. Default is 25.
|
|
Lx_taper : float, optional
|
|
Length parameter in microns. Default is 50.
|
|
Ly_taper : float, optional
|
|
Length parameter in microns. Default is 0.
|
|
Lx_end : float, optional
|
|
Length parameter in microns. Default is 1.
|
|
Ly_end : float, optional
|
|
Length parameter in microns. Default is 1.
|
|
Lx_side : float, optional
|
|
Length parameter in microns. Default is 0.5.
|
|
Ly_side : float, optional
|
|
Length parameter in microns. Default is 0.5.
|
|
Lx_port : float, optional
|
|
Length parameter in microns. Default is 5.
|
|
Ly_port : float, optional
|
|
Length parameter in microns. Default is 5.
|
|
w_wg : float, optional
|
|
Width parameter in microns. Default is 0.5.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
P_AR : float, optional
|
|
Value for the P_AR parameter. Default is 0.6.
|
|
L_AR : float, optional
|
|
Length parameter in microns. Default is 1.
|
|
"""
|
|
def __init__(self,
|
|
name: Optional[str]=None,
|
|
etch_type :str = 'FETCH',
|
|
xs_wg:str='grating',
|
|
Dx_hole:float=0.3,
|
|
Dy_hole:float=0.3,
|
|
hole_shape :str= 'circle',
|
|
shape:str = 'circle',
|
|
xs_open:str = None,
|
|
Px:float=0.57,
|
|
Py:float=0.57,
|
|
num_x:float=25,
|
|
num_y:float=25,
|
|
Lx_taper:float = 50,
|
|
Ly_taper:float = 0,
|
|
Lx_end:float = 1,
|
|
Ly_end:float = 1,
|
|
Lx_side:float = 0.5,
|
|
Ly_side:float = 0.5,
|
|
Lx_port:float=5,
|
|
Ly_port:float=5,
|
|
w_wg:float=0.5,
|
|
show_pins:bool=False,
|
|
P_AR: float = 0.6,
|
|
L_AR: float = 1,
|
|
) -> None:
|
|
"""_summary_
|
|
|
|
Args:
|
|
etch_type (str, optional): Three etch depth for election , full-etch = "FETCH", middle etch = "METCH", shallow etch = "ETCH". Defaults to 'FETCH'.
|
|
xs_wg (str, optional): xsection of the grating and also the output waveguide. Defaults to 'grating'.
|
|
Dx_hole (float, optional): size X of the hole, when in 'circle' hole selection ,this is the Diameter of your hole . Defaults to 0.3.
|
|
Dy_hole (float, optional): size Y of the hole, when in 'circle' hole selection ,this is the Diameter of your hole . Defaults to 0.3.
|
|
hole_shape (str, 'circle' | 'rectangel'): shape of the hole. Defaults to 'circle'.
|
|
shape (str, 'circle' | 'rectangel'): shape of the grating. Defaults to 'circle'.
|
|
Px (float, optional): Period distance X. Defaults to 0.57.
|
|
Py (float, optional): Period distance Y. Defaults to 0.57.
|
|
num_x (int, optional): number of pitches. Defaults to 25.
|
|
num_y (int, optional): number of pitches. Defaults to 25.
|
|
Lx_taper (int, optional): taper connection to the port. Defaults to 50.
|
|
Ly_taper (int, optional): taper connection to the port. Defaults to 0.
|
|
Lx_end (int, optional): length arratched to the end. Defaults to 5.
|
|
Ly_end (int, optional): length arratched to the end. Defaults to 3.
|
|
Lx_side (float, optional): side expansion. Defaults to 0.5.
|
|
Ly_side (float, optional): side expansion. Defaults to 0.5.
|
|
Lx_port (int, optional): output port length expansion. Defaults to 5.
|
|
Ly_port (int, optional): output port length expansion. Defaults to 5.
|
|
w_wg (float, optional): output port width. Defaults to 0.5.
|
|
show_pins (bool, optional): _description_. Defaults to False.
|
|
|
|
Raises:
|
|
Exception: Period do not match D_hole
|
|
"""
|
|
self.name = name
|
|
if (self.name==None):
|
|
self.instantiate = False
|
|
else :
|
|
self.instantiate = True
|
|
|
|
if (isinstance(Px,int) or isinstance(Px,float)) : Px = Px * np.ones(num_x)
|
|
if (isinstance(Py,int) or isinstance(Py,float)) : Py = Py * np.ones(num_y)
|
|
if (isinstance(Dx_hole,int) or isinstance(Dx_hole,float)) : Dx_hole = Dx_hole * np.ones((num_x))
|
|
if (isinstance(Dy_hole,int) or isinstance(Dy_hole,float)) : Dy_hole = Dy_hole * np.ones((num_y))
|
|
|
|
self.num_x = len(Px)
|
|
self.num_y = len(Py)
|
|
if (len(Px)!=len(Dx_hole) or len(Py)!=len(Dy_hole)):
|
|
raise Exception("In Grating define : [Period] length not matching [D_hole] length")
|
|
|
|
self.Lx_taper = Lx_taper
|
|
self.Ly_taper = Ly_taper
|
|
self.Lx_end = Lx_end
|
|
self.Ly_end = Ly_end
|
|
self.Lx_side = Lx_side
|
|
self.Ly_side = Ly_side
|
|
self.Lx_port = Lx_port
|
|
self.Ly_port = Ly_port
|
|
|
|
self.xs_open = xs_open
|
|
|
|
self.w_wg = w_wg
|
|
self.xs_wg = xs_wg
|
|
self.etch_type = etch_type
|
|
self.shape = shape
|
|
|
|
self.hole_shape = hole_shape
|
|
self.Dx_hole = Dx_hole
|
|
self.Dy_hole = Dy_hole
|
|
self.Px = Px
|
|
self.Py = Py
|
|
|
|
self.P_AR = P_AR
|
|
self.L_AR = L_AR
|
|
|
|
|
|
self.show_pins = show_pins
|
|
|
|
|
|
if (nd.get_layer(layer="STRIP_TRE") == "STRIP_TRE"):
|
|
self.positive = False
|
|
if (hole_shape=='circle'):
|
|
if (etch_type=="FETCH"):
|
|
layer_etch = "STRIP_HOL"
|
|
elif (etch_type=="METCH"):
|
|
layer_etch = "RIB_HOL"
|
|
elif (etch_type=="SETCH"):
|
|
layer_etch = "SRIB_HOL"
|
|
|
|
elif (hole_shape=='rectangle'):
|
|
if (etch_type=="FETCH"):
|
|
layer_etch = "STRIP_TRE"
|
|
elif (etch_type=="METCH"):
|
|
layer_etch = "RIB_TRE"
|
|
elif (etch_type=="SETCH"):
|
|
layer_etch = "SRIB_TRE"
|
|
else :
|
|
self.positive = True
|
|
|
|
if (etch_type=="FETCH"):
|
|
layer_etch = None
|
|
elif (etch_type=="METCH"):
|
|
layer_etch = "RIB_COR"
|
|
elif (etch_type=="SETCH"):
|
|
layer_etch = "SRIB_COR"
|
|
|
|
self.layer_etch = layer_etch
|
|
|
|
if (layer_etch!=None):
|
|
if (nd.get_layer(layer_etch)!=layer_etch):
|
|
layer_etch=None
|
|
print("WARNING: In mxpic::passive::GC_STD_1D, <layer_etch>::",layer_etch," not defined in tapeout")
|
|
|
|
if (self.positive):
|
|
self.cell = self.generate_positive()
|
|
else:
|
|
self.cell = self.generate_negative()
|
|
|
|
def generate_negative(self):
|
|
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
|
|
|
|
## arc shape grating
|
|
if (self.shape=='circle' or self.shape=='arc'):
|
|
print("Sorry, this function has not been built up")
|
|
|
|
## retangular grating
|
|
elif (self.shape=='rectangle'):
|
|
Lx = sum(self.Px)+self.Lx_side*2
|
|
# if (self.Ly_taper==0):
|
|
Ly = sum(self.Py)+self.Ly_side*2
|
|
# else:
|
|
# Ly = sum(self.Py)
|
|
y_offset = sum(self.Py)/2
|
|
x_offset = sum(self.Px)/2
|
|
nd.strt(length=Lx,width=Ly,xs=self.xs_wg).put(-Lx/2,0,0)
|
|
|
|
|
|
if (self.xs_open!=None):
|
|
circle(radius=max([Lx,Ly])*2/2,width=max([Lx,Ly])*2,xs=self.xs_open,
|
|
# n_points=32
|
|
).cell.put(0,0,0)
|
|
|
|
|
|
for _x_ in range(0,self.num_x):
|
|
for _y_ in range(0,self.num_y):
|
|
pos_x = np.sum(self.Px[0:_x_+1])-self.Px[0]/2-x_offset
|
|
pos_y = np.sum(self.Py[0:_y_+1])-y_offset-self.Py[0]/2
|
|
if (self.hole_shape=='circle'):
|
|
circle(radius=self.Dx_hole[_x_]/4,width=self.Dx_hole[_x_]/2,layer=self.layer_etch,
|
|
# n_points=32,
|
|
sharp_patch=False).cell.put(pos_x,pos_y,0)
|
|
elif (self.hole_shape=='rectangle'):
|
|
nd.strt(length=self.Dx_hole[_x_],width=self.Dy_hole[_y_],layer=self.layer_etch).put(pos_x-self.Dx_hole[_x_]/2,pos_y,0)
|
|
else :
|
|
raise Exception("ERROR: In <mxpic::passive::GC_STD_2D>, <hole_shape> is not defined, please input [circle | rectangle]")
|
|
if (self.Ly_taper!=0):
|
|
nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(0,Ly/2,90)
|
|
|
|
if (self.P_AR>0):
|
|
_num_AR_ = int(np.floor(self.L_AR/self.P_AR)+1)
|
|
for _idx_ in range(0,_num_AR_):
|
|
# nd.strt(xs=self.xs_wg,width=Lx,length=self.P_AR/2).put(0,self.P_AR/2+Ly/2+self.Ly_end+self.P_AR*_idx_,90)
|
|
nd.strt(xs=self.xs_wg,width=self.P_AR/2,length=Lx).put(-Lx/2,self.P_AR+Ly/2+self.Ly_end+self.P_AR*_idx_,0)
|
|
# if (self.P_AR >0 and self.L_AR>0):
|
|
# _num_AR_ = int(np.floor(Ly/self.P_AR))
|
|
# for _idx_ in range(0,_num_AR_):
|
|
# nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR+Lx/2,Ly/2+self.Ly_end,90)
|
|
|
|
nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(0,-Ly/2,-90)
|
|
y_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg,length=self.Ly_taper).put()
|
|
y_port = nd.strt(length=self.Ly_port,width=self.w_wg,xs=self.xs_wg).put()
|
|
nd.Pin(name='g2',pin=y_port.pin['b0']).put()
|
|
|
|
nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(Lx/2,0,0)
|
|
|
|
## adding anti reflection
|
|
if (self.Lx_taper!=0):
|
|
if (self.P_AR>0):
|
|
_num_AR_ = int(np.floor(self.L_AR/self.P_AR)+1)
|
|
for _idx_ in range(0,_num_AR_):
|
|
# nd.strt(xs=self.xs_wg,width=Lx,length=self.P_AR/2).put(self.P_AR/2+Lx/2+self.Lx_end+self.P_AR*_idx_,0,0)
|
|
nd.strt(xs=self.xs_wg,length=Ly,width=self.P_AR/2).put(self.P_AR+Lx/2+self.Lx_end+self.P_AR*_idx_,-Ly/2,90)
|
|
|
|
# _num_AR_ = int(np.floor(Ly/self.P_AR))
|
|
# for _idx_ in range(0,_num_AR_):
|
|
# nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(Lx+self.Lx_end,_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR,0)
|
|
|
|
nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(-Lx/2,0,180)
|
|
x_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg,length=self.Lx_taper).put()
|
|
x_port = nd.strt(length=self.Lx_port,width=self.w_wg,xs=self.xs_wg).put()
|
|
nd.Pin(name='g1',pin=x_port.pin['b0']).put()
|
|
|
|
# print("Sorry, this function has not been built up")
|
|
|
|
else :
|
|
raise Exception("In Grating define : [shape] not defined")
|
|
|
|
if (self.show_pins):
|
|
nd.put_stub(pinsize=3)
|
|
return C
|
|
|
|
def generate_positive(self):
|
|
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
|
|
|
|
## arc shape grating
|
|
if (self.shape=='circle' or self.shape=='arc'):
|
|
print("Sorry, this function has not been built up")
|
|
pass
|
|
|
|
## retangular grating
|
|
elif (self.shape=='rectangle'):
|
|
Lx = sum(self.Px)
|
|
if (self.Ly_taper==0):
|
|
Ly = sum(self.Py)+self.Ly_side*2
|
|
nd.strt(length=Lx,width=self.Ly_side,xs=self.xs_wg).put(0,sum(self.Py)/2+self.Lx_side/2,0)
|
|
nd.strt(length=Lx,width=self.Ly_side,xs=self.xs_wg).put(0,-sum(self.Py)/2-self.Lx_side/2,0)
|
|
else:
|
|
Ly = sum(self.Py)
|
|
y_offset = sum(self.Py)/2
|
|
if (self.layer_etch!=None):
|
|
nd.strt(length=Lx,width=Ly,layer=self.layer_etch).put(0,0,0)
|
|
if (self.xs_open!=None):
|
|
nd.strt(length=Lx*2,width=Ly*2,xs=self.xs_open).put(-Lx/2,0,0)
|
|
|
|
for _x_ in range(0,self.num_x):
|
|
for _y_ in range(0,self.num_y):
|
|
pos_x = np.sum(self.Px[0:_x_+1])-self.Px[0]/2
|
|
pos_y = np.sum(self.Py[0:_y_+1])-y_offset-self.Py[0]/2
|
|
hole(r_hole=self.Dx_hole[_x_]/2,Lx_sq=self.Px[_x_],Ly_sq=self.Py[_y_],
|
|
Dx_hole=self.Dx_hole[_x_],Dy_hole=self.Dy_hole[_y_],
|
|
# n_points=12,
|
|
xs=self.xs_wg,hole_shape=self.hole_shape).cell.put(pos_x,pos_y,0)
|
|
|
|
if (self.Ly_taper!=0):
|
|
nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(Lx/2,Ly/2,90)
|
|
_num_AR_ = int(np.floor(Ly/self.P_AR))
|
|
for _idx_ in range(0,_num_AR_):
|
|
nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR+Lx/2,Ly/2+self.Ly_end,90)
|
|
|
|
nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(Lx/2,-Ly/2,-90)
|
|
y_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg,length=self.Lx_taper).put()
|
|
y_port = nd.strt(length=self.Ly_port,width=self.w_wg,xs=self.xs_wg).put()
|
|
nd.Pin(name='g2',pin=y_port.pin['b0']).put()
|
|
|
|
nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(Lx,0,0)
|
|
## adding anti reflection
|
|
_num_AR_ = int(np.floor(Ly/self.P_AR))
|
|
for _idx_ in range(0,_num_AR_):
|
|
nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(Lx+self.Lx_end,_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR,0)
|
|
|
|
nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(0,0,180)
|
|
x_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg).put()
|
|
x_port = nd.strt(length=self.Lx_port,width=self.w_wg,xs=self.xs_wg).put()
|
|
nd.Pin(name='g1',pin=x_port.pin['b0']).put()
|
|
else :
|
|
raise Exception("In Grating define : [shape] not defined")
|
|
if (self.show_pins):
|
|
nd.put_stub(pinsize=3)
|
|
return C
|
|
|
|
def generate_test_gds(self,dX_gc2gc=300):
|
|
with nd.Cell(instantiate=False) as C:
|
|
self.cell.put('g1',-dX_gc2gc/2,0,180)
|
|
self.cell.put('g1', dX_gc2gc/2,0,0)
|
|
|
|
nd.strt(xs=self.xs_wg,width=self.w_wg,length=dX_gc2gc).put(-dX_gc2gc/2,0,0)
|
|
|
|
return C
|
|
|
|
class GC_STD_1D:
|
|
"""
|
|
GC STD 1D primitive component.
|
|
|
|
This component builds the GC STD 1D layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
name : Optional[str], optional
|
|
Unique identifier for the device cell. Default is None.
|
|
xs_wg : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
w_wg : float, optional
|
|
Width parameter in microns. Default is 0.5.
|
|
etch_type : str, optional
|
|
Value for the etch_type parameter. Default is 'FETCH'.
|
|
xs_open : str, optional
|
|
Layer or cross-section name used by the device. Default is None.
|
|
L_taper : float, optional
|
|
Length parameter in microns. Default is 10.
|
|
L_end : float, optional
|
|
Length parameter in microns. Default is 2.
|
|
A_taper : float, optional
|
|
Angle parameter in degrees. Default is 30.
|
|
Period : float, optional
|
|
Value for the Period parameter. Default is 0.5.
|
|
eta_etch : float, optional
|
|
Value for the eta_etch parameter. Default is 0.5.
|
|
num : float, optional
|
|
Count or repetition parameter. Default is 20.
|
|
sector_gc : bool, optional
|
|
Value for the sector_gc parameter. Default is True.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
L_tail : int, optional
|
|
Length parameter in microns. Default is 2.
|
|
P_AR : float, optional
|
|
Value for the P_AR parameter. Default is 1.
|
|
L_AR : float, optional
|
|
Length parameter in microns. Default is 2.
|
|
"""
|
|
def __init__ (self,
|
|
name: Optional[str]=None,
|
|
xs_wg : str = 'strip',
|
|
w_wg : float = 0.5,
|
|
etch_type :str = 'FETCH',
|
|
xs_open :str=None,
|
|
L_taper :float = 10,
|
|
L_end :float = 2,
|
|
A_taper :float = 30,
|
|
Period :float = 0.5,
|
|
eta_etch :float = 0.5,
|
|
num :float = 20, ### note, when Period and eta is defined as list, this is not usefull
|
|
sector_gc :bool =True,
|
|
show_pins: bool=False,
|
|
L_tail: int = 2,
|
|
# n_points = 64,
|
|
P_AR: float = 1, ### adding anti reflection pitches
|
|
L_AR: float = 2,
|
|
) -> None:
|
|
|
|
self.name = name
|
|
if (self.name==None):
|
|
self.instantiate = False
|
|
else :
|
|
self.instantiate = True
|
|
|
|
if (xs_open!=None):
|
|
try: nd.get_xsection(xs_open)
|
|
except:
|
|
xs_open=None
|
|
print("WARNING:In <mxpic::passive::GC_STD_1D>, <xs_open>::",xs_open," not defined in tapeout")
|
|
self.xs_open=xs_open
|
|
|
|
|
|
self.xs_wg=xs_wg
|
|
self.w_wg=w_wg
|
|
self.L_taper=L_taper
|
|
self.L_end=L_end
|
|
self.A_taper=A_taper
|
|
self.show_pins = show_pins
|
|
self.L_tail = L_tail
|
|
|
|
if (isinstance(eta_etch,list) or isinstance(eta_etch,np.ndarray)):
|
|
num = len(eta_etch)
|
|
|
|
if (isinstance(Period,list) or isinstance(Period,np.ndarray)):
|
|
num = len(Period)
|
|
|
|
|
|
|
|
if (isinstance(Period,int) or isinstance(Period,float)):
|
|
Period = Period*np.ones(num)
|
|
|
|
if (isinstance(eta_etch,int) or isinstance(eta_etch,float)):
|
|
eta_etch = eta_etch*np.ones(num)
|
|
|
|
""" Generate ERROR """
|
|
if (len(Period)!=len(eta_etch)):
|
|
raise Exception("ERROR: In <mxpic::passive::GC_STD_1D> : [Period] length not matching [eta_etch] length")
|
|
|
|
if (nd.get_layer(layer="STRIP_TRE") == "STRIP_TRE"):
|
|
self.positive = False
|
|
if (etch_type=="FETCH"):
|
|
layer_etch = "STRIP_TRE"
|
|
elif (etch_type=="METCH"):
|
|
layer_etch = "RIB_TRE"
|
|
elif (etch_type=="SETCH"):
|
|
layer_etch = "SRIB_TRE"
|
|
else :
|
|
self.positive = True
|
|
|
|
if (etch_type=="FETCH"):
|
|
layer_etch = None
|
|
elif (etch_type=="METCH"):
|
|
layer_etch = "RIB_COR"
|
|
elif (etch_type=="SETCH"):
|
|
layer_etch = ["SRIB_COR","RIB_COR"]
|
|
|
|
self.Period=Period
|
|
self.eta_etch=eta_etch
|
|
self.num=len(Period)
|
|
self.sector_gc=sector_gc
|
|
|
|
# self.n_points = n_points ## revise 2022.08.18
|
|
|
|
self.L_AR = L_AR
|
|
self.P_AR = P_AR
|
|
|
|
if (layer_etch!=None):
|
|
if (isinstance(layer_etch,str)):
|
|
if (nd.get_layer(layer_etch)!=layer_etch):
|
|
layer_etch=None
|
|
print("WARNING: In mxpic::passive::GC_STD_1D, <layer_etch>::",layer_etch," not defined in tapeout")
|
|
else :
|
|
for _layer_ in layer_etch:
|
|
if (nd.get_layer(_layer_)!=_layer_):
|
|
layer_etch=None
|
|
print("WARNING: In mxpic::passive::GC_STD_1D, <layer_etch>::",layer_etch," not defined in tapeout")
|
|
self.layer_etch = layer_etch
|
|
|
|
if (self.positive):
|
|
self.cell = self.generate_positive()
|
|
else:
|
|
self.cell = self.generate_negative()
|
|
|
|
def generate_negative(self):
|
|
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
|
|
|
|
## arc shape grating
|
|
if (self.sector_gc == True):
|
|
L_total = np.sum(self.Period) + self.L_taper + self.L_end
|
|
L_tail = self.L_tail
|
|
for layers,growx,growy,acc in nd.layeriter(xs=self.xs_wg):
|
|
(a1,b1), (a2,b2),c1,c2 = growx
|
|
x_offset = -b1/np.sin(self.A_taper/2*np.pi/180)
|
|
|
|
if (self.P_AR>0 and self.L_AR>0): ## anti reflection
|
|
r_tap = L_total*1.3 - b1 - x_offset
|
|
else :
|
|
r_tap = L_total + b1 - x_offset
|
|
|
|
circle(radius=r_tap/2,width=r_tap,
|
|
theta_start=-self.A_taper/2,theta_stop=self.A_taper/2,
|
|
# n_points=self.n_points,
|
|
layer=layers).cell.put(x_offset,0,0)
|
|
_L_tail_ = np.abs(x_offset)
|
|
if _L_tail_ > L_tail:
|
|
L_tail = _L_tail_
|
|
|
|
|
|
r_grat_inner = self.L_taper
|
|
for _idx_ in range(0,self.num):
|
|
d_pitch = self.Period[_idx_]*self.eta_etch[_idx_]
|
|
circle(radius=r_grat_inner + d_pitch/2,
|
|
width=d_pitch,
|
|
theta_start=-self.A_taper/2-5,theta_stop=self.A_taper/2+5,
|
|
# n_points=self.n_points,
|
|
layer=self.layer_etch).cell.put(0,0,0)
|
|
r_grat_inner = r_grat_inner + self.Period[_idx_]
|
|
|
|
L_open = 1.5*(L_total-self.L_taper)
|
|
W_open = 1.1*(L_total*np.tan(self.A_taper/2*np.pi/180))*2
|
|
x_open = (L_total+self.L_taper)/2 - L_open/2
|
|
|
|
if (self.xs_open!=None):
|
|
nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0)
|
|
|
|
nd.strt(length=L_tail,width=self.w_wg,xs=self.xs_wg).put(-L_tail,0,0)
|
|
nd.Pin(name='g1',width=self.w_wg).put(-L_tail,0,180)
|
|
nd.strt(length=np.abs(self.w_wg/2/np.tan(self.A_taper/2/180*np.pi)),width=self.w_wg,xs=self.xs_wg).put(0,0,0)
|
|
|
|
|
|
## retangular grating
|
|
else:
|
|
|
|
L_total = np.sum(self.Period) + self.L_taper + self.L_end
|
|
|
|
L_grat = sum(self.Period)+self.L_end
|
|
W_grat = self.w_wg + self.L_taper*np.tan(self.A_taper/2*np.pi/180)*2
|
|
nd.taper(length=self.L_taper,width1=self.w_wg,width2=W_grat,xs=self.xs_wg).put(0,0,0)
|
|
nd.strt(length=L_grat,width=W_grat,xs=self.xs_wg).put(self.L_taper,0,0)
|
|
|
|
### adding Anti-reflection
|
|
|
|
# if (self.P_AR>0 and self.L_AR>0):
|
|
# _num_AR_ = int(np.floor(W_grat/self.P_AR))
|
|
# for _idx_ in range(0,_num_AR_):
|
|
# nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(self.L_taper+L_grat, (_idx_ - (_num_AR_-1)/2)*self.P_AR,0)
|
|
|
|
|
|
x_grat = self.L_taper
|
|
for _idx_ in range(0,self.num):
|
|
nd.strt(length=self.Period[_idx_]*self.eta_etch[_idx_],width=W_grat+2,layer=self.layer_etch).put(x_grat,0,0)
|
|
x_grat = x_grat + self.Period[_idx_]
|
|
|
|
nd.strt(length=10,width=self.w_wg,xs=self.xs_wg).put(-5,0,0)
|
|
nd.Pin(name='g1',width=self.w_wg).put(-5,0,180)
|
|
|
|
L_open = 1.5*(L_total-self.L_taper)
|
|
W_open = 1.1*(L_total*np.tan(self.A_taper/2*np.pi/180))*2
|
|
x_open = (L_total+self.L_taper)/2 - L_open/2
|
|
if (self.xs_open!=None):
|
|
nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0)
|
|
pass
|
|
|
|
|
|
if (self.show_pins):
|
|
nd.put_stub()
|
|
return C
|
|
|
|
def generate_positive(self):
|
|
with nd.Cell(instantiate=False) as C:
|
|
## arc shape grating
|
|
if (self.sector_gc==True):
|
|
L_tail = self.L_tail
|
|
|
|
for layers,growx,growy,acc in nd.layeriter(xs=self.xs_wg):
|
|
(a1,b1), (a2,b2),c1,c2 = growx
|
|
x_offset = -b1/np.sin(self.A_taper/2*np.pi/180)
|
|
|
|
r_tap = self.L_taper + b1 - x_offset
|
|
|
|
circle(radius=r_tap/2,width=r_tap,
|
|
theta_start=-self.A_taper/2,theta_stop=self.A_taper/2,
|
|
# n_points=self.n_points,
|
|
layer=layers).cell.put(x_offset,0,0)
|
|
|
|
r_grat_inner = self.L_taper - x_offset
|
|
|
|
_L_tail_ = np.abs(x_offset)
|
|
if _L_tail_ > L_tail:
|
|
L_tail = _L_tail_
|
|
|
|
for _idx_ in range(0,self.num):
|
|
|
|
d_pitch = self.Period[_idx_]*(1-self.eta_etch[_idx_])+b1*2
|
|
|
|
|
|
circle(radius=r_grat_inner + self.Period[_idx_]*self.eta_etch[_idx_] + d_pitch/2,
|
|
width=d_pitch,
|
|
theta_start=-self.A_taper/2,theta_stop=self.A_taper/2,
|
|
# n_points=self.n_points,
|
|
layer=layers).cell.put(x_offset,0,0)
|
|
|
|
r_grat_inner = r_grat_inner + self.Period[_idx_]
|
|
|
|
d_pitch = self.L_end+b1*2
|
|
r_grat_inner = r_grat_inner + self.Period[-1]*self.eta_etch[-1]
|
|
|
|
circle(radius=r_grat_inner + d_pitch/2,
|
|
width=d_pitch,
|
|
theta_start=-self.A_taper/2,theta_stop=self.A_taper/2,
|
|
# n_points=64,
|
|
layer=layers).cell.put(x_offset,0,0)
|
|
nd.strt(length=L_tail*2,width=self.w_wg,xs=self.xs_wg).put(-L_tail,0,0)
|
|
nd.Pin(name='g1',width=self.w_wg).put(-L_tail,0,180)
|
|
|
|
L_total = np.sum(self.Period) + self.L_taper + self.L_end
|
|
L_open = 1.5*(L_total-self.L_taper)
|
|
W_open = 1.2*(L_total*np.tan(self.A_taper/2*np.pi/180))*2
|
|
x_open = (L_total+self.L_taper)/2 - L_open/2
|
|
if (self.layer_etch!=None):
|
|
if (isinstance(self.layer_etch,str)):
|
|
nd.strt(length=L_open,width=W_open,layer=self.layer_etch).put(x_open,0,0)
|
|
elif(isinstance(self.layer_etch,list)):
|
|
for _layer_ in self.layer_etch:
|
|
nd.strt(length=L_open,width=W_open,layer=_layer_).put(x_open,0,0)
|
|
|
|
if (self.xs_open!=None):
|
|
nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0)
|
|
|
|
|
|
|
|
## retangular grating
|
|
elif (self.sector_gc==False):
|
|
|
|
L_grat = sum(self.Period)
|
|
W_grat = self.w_wg + self.L_taper*np.tan(self.A_taper/2*np.pi/180)*2
|
|
nd.taper(length=self.L_taper,width1=self.w_wg,width2=W_grat,xs=self.xs_wg).put(0,0,0)
|
|
|
|
x_grat = self.L_taper
|
|
for _idx_ in range(0,self.num):
|
|
nd.strt(length=self.Period[_idx_]*(1-self.eta_etch[_idx_]),width=W_grat,xs=self.xs_wg).put(x_grat+self.Period[_idx_]*self.eta_etch[_idx_],0,0)
|
|
x_grat = x_grat + self.Period[_idx_]
|
|
|
|
nd.strt(length=10,width=self.w_wg,xs=self.xs_wg).put(-5,0,0)
|
|
nd.Pin(name='g1',width=self.w_wg).put(-5,0,180)
|
|
|
|
### adding Anti-reflection
|
|
if (self.P_AR>0 and self.L_AR>0):
|
|
_num_AR_ = int(np.floor(W_grat/self.P_AR))
|
|
for _idx_ in range(0,_num_AR_):
|
|
nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(self.L_taper+L_grat, (_idx_ - (_num_AR_-1)/2)*self.P_AR,0)
|
|
|
|
|
|
L_total = np.sum(self.Period) + self.L_taper + self.L_end
|
|
L_open = 1.5*(L_total-self.L_taper)
|
|
W_open = 1.1*(L_total*np.tan(self.A_taper/2*np.pi/180))*2
|
|
x_open = (L_total+self.L_taper)/2 - L_open/2
|
|
if (self.layer_etch!=None):
|
|
nd.strt(length=L_open,width=W_open,layer=self.layer_etch).put(x_open,0,0)
|
|
|
|
if (self.xs_open!=None):
|
|
nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0)
|
|
|
|
|
|
pass
|
|
else :
|
|
raise Exception("In Grating define : [shape] not defined")
|
|
if (self.show_pins):
|
|
nd.put_stub()
|
|
return C
|
|
|
|
def generate_test_dev(self,dX_gc2gc):
|
|
with nd.Cell(instantiate=False) as C:
|
|
self.cell.put('g1',-dX_gc2gc/2,0,180)
|
|
self.cell.put('g1', dX_gc2gc/2,0,0)
|
|
nd.strt(xs=self.xs_wg,width=self.w_wg,length=dX_gc2gc).put(-dX_gc2gc/2,0,0)
|
|
return C
|
|
|
|
class FA:
|
|
"""
|
|
FA primitive component.
|
|
|
|
This component builds the FA layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
fiber_coupler : Any
|
|
Fiber coupler cell or component used by this array.
|
|
pitch : float
|
|
Spacing or gap parameter in microns.
|
|
number : int
|
|
Count or repetition parameter.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
"""
|
|
def __init__(self,fiber_coupler: Any,pitch: float,number: int,show_pins: bool=False) -> None:
|
|
|
|
# if (isinstance(fiber_coupler,nd.Cell)):
|
|
# fiber_cell = fiber_coupler
|
|
|
|
# elif (hasattr(fiber_coupler,'cell')):
|
|
# fiber_cell = fiber_coupler.cell
|
|
# else:
|
|
# raise Exception("ERROR: In <mxpic.passive.FA>, <fiber_coupler> not recongized, please input nazca.cell or classes that has nazca.cell")
|
|
|
|
fiber_cell = __cell_arg__(arg=fiber_coupler,arg_name="fiber_coupler",func_name="mxpic::FA")
|
|
|
|
|
|
pin_in_name = []
|
|
for name,Pin in fiber_cell.ic_pins():
|
|
|
|
pin_in_name = pin_in_name+[name]
|
|
# pin_in_name.append(name)
|
|
|
|
if ('g1' in pin_in_name):
|
|
pin_name = 'g1'
|
|
else:
|
|
pin_name = 'a0'
|
|
print("WARNING: In <mxpic.passive.FA>, <fiber_coupler> dose not contain 'g1' pin, using 'a0' in default")
|
|
|
|
self.pitch = pitch
|
|
self.number = number
|
|
|
|
with nd.Cell(instantiate=False) as C:
|
|
|
|
for idx in range(1,number+1):
|
|
port = fiber_cell.put(pin_name,0,pitch*(idx-number/2-1/2),0)
|
|
nd.Pin('g'+str(idx),pin=port.pin[pin_name]).put()
|
|
|
|
x_out = port.pin['b0'].x
|
|
nd.Pin(name='b0').put(x_out,0,180)
|
|
|
|
if (show_pins):
|
|
nd.put_stub(pinsize=3)
|
|
self.cell = C
|
|
|