grating_coupler
The grating coupler is an important component that couples the light source from the fiber to the chip or couples the on-chip light source to the fiber.
The building steps are as follows:
Import library:
import math
from typing import List, Tuple, cast
from fnpcell import all as fp
from gpdk.technology import get_technology, PCell
from gpdk.technology.interfaces import CoreCladdingWaveguideType
Define class GratingCoupler:
class GratingCoupler(PCell, band="C"):
"""
Attributes:
length: defaults to 25.0
half_degrees: defaults to 20
ellipse_ratio: defaults to 1.0, Ellipse(Major/Minor)
tooth_width: defaults to 0.5
etch_width: defaults to 0.5
teeth: defaults to 30
waveguide_type: type of waveguide
port_names: defaults to ["op_0"]
Examples:
```python
TECH = get_technology()
gc = GratingCoupler(name="f", etch_width=0.5, tooth_width=0.5, length=25, half_degrees=40, teeth=30, waveguide_type=TECH.WG.FWG.C.WIRE)
fp.plot(gc)
```

"""
length: float = fp.PositiveFloatParam(default=25.0)
half_degrees: float = fp.DegreeParam(default=20)
ellipse_ratio: float = fp.PositiveFloatParam(default=1.0, min=1.0, doc="Ellipse(Major/Minor)")
tooth_width: float = fp.PositiveFloatParam(default=0.5)
etch_width: float = fp.PositiveFloatParam(default=0.5)
teeth: int = fp.IntParam(default=30, min=0, doc="Number of tooth")
waveguide_type: CoreCladdingWaveguideType = fp.WaveguideTypeParam(type=CoreCladdingWaveguideType)
port_names: fp.IPortOptions = fp.PortOptionsParam(count=1, default=["op_0"])
def _default_waveguide_type(self):
return get_technology().WG.FWG.C.WIRE
def build(self) -> Tuple[fp.InstanceSet, fp.ElementSet, fp.PortSet]:
insts, elems, ports = super().build()
TECH = get_technology()
# fmt: off
length = self.length
half_degrees=self.half_degrees
ellipse_ratio = self.ellipse_ratio
tooth_width = self.tooth_width
etch_width = self.etch_width
teeth = self.teeth
waveguide_type = self.waveguide_type
port_names = self.port_names
overlap = 1.0
fiber_pin_width = 5
half_angle = math.radians(half_degrees)
waveguide_width = waveguide_type.core_width
waveguide_cladding = waveguide_type.cladding_width
waveguide_layer = waveguide_type.core_layer
cladding_layer = waveguide_type.cladding_layer
si_etch1_layer = TECH.WG.MWG.C.WIRE.core_layer
fbrtgt = TECH.LAYER.FIBREC_NOTE
content:List[fp.IPolygon] = []
# move gap line to the other side
content.append(
fp.el.EllipticalRing(outer_radius=(length, length / ellipse_ratio), layer=waveguide_layer, transform=fp.h_mirror()))
final_tooth_radius = length
for _ in range(teeth):
final_tooth_radius = final_tooth_radius + etch_width
inner_radius_x = final_tooth_radius
inner_radius_y = inner_radius_x / ellipse_ratio
final_tooth_radius = final_tooth_radius + tooth_width
outer_radius_x = final_tooth_radius
outer_radius_y = outer_radius_x / ellipse_ratio
# move gap line to the other side
content.append(fp.el.EllipticalRing(outer_radius=(outer_radius_x, outer_radius_y), inner_radius=(inner_radius_x, inner_radius_y), layer=waveguide_layer, transform=fp.h_mirror()))
delta_radius = (waveguide_width / 2.0) / math.tan(half_angle)
wedge_y = math.tan(half_angle) * (delta_radius + final_tooth_radius)
trapezoid = fp.el.Line(length=final_tooth_radius, stroke_width=waveguide_width, final_stroke_width=wedge_y * 2, layer=waveguide_layer)
content = list(fp.el.PolygonSet(content, layer=waveguide_layer) & trapezoid)
fiber_pin_tooth = 1 + int(teeth / 2) # 1 for wedge_polygon
fiber_pin_x = min(content[fiber_pin_tooth].polygon_points, key=lambda p: p[0])[0]
overlap_x = final_tooth_radius + overlap
overlap_y = overlap_x / ellipse_ratio
overlap_polygon = fp.el.EllipticalRing(outer_radius=(overlap_x, overlap_y), layer=si_etch1_layer, transform=fp.rotate(radians=math.pi))
inner_angle = math.pi / 2 - half_angle
perpendicular_overlap = overlap / math.sin(inner_angle)
overlap_delta = (perpendicular_overlap + (waveguide_width / 2)) / math.tan(half_angle)
overlap_wedge_y = math.tan(half_angle) * (overlap_delta + final_tooth_radius + overlap)
# overlap_wedge_x = overlap_delta + final_tooth_radius + overlap
trapezoid = fp.el.Line(length=overlap_x, stroke_width=waveguide_width + perpendicular_overlap * 2, final_stroke_width=overlap_wedge_y * 2, layer=si_etch1_layer)
overlap_polygon &= trapezoid
# content.append(overlap_polygon) # temporary commented for Circuit 01
cladding_x = final_tooth_radius + waveguide_cladding / 2
cladding_y = cladding_x / ellipse_ratio
cladding_polygon = fp.el.EllipticalRing(outer_radius=(cladding_x, cladding_y), layer=cladding_layer, transform=fp.rotate(radians=math.pi))
trapezoid = fp.el.Line(length=cladding_x, stroke_width=waveguide_cladding, final_stroke_width=math.tan(half_angle) * cladding_x * 2 + waveguide_cladding, layer=cladding_layer)
cladding_polygon &= trapezoid
content.extend(cladding_polygon)
# fiber port
elements = cast(List[fp.IElement], content)
elements.extend(
[
fp.el.Line(length=fiber_pin_width, stroke_width=fiber_pin_width, layer=fbrtgt, transform=fp.translate(fiber_pin_x, 0)),
fp.el.Text(content="optFiber", text_anchor=fp.Anchor.CENTER, vertical_align=fp.VertialAlign.MIDDLE, layer=fbrtgt, at=(fiber_pin_x + fiber_pin_width / 2, 0)),
]
)
ports += fp.Port(name=port_names[0], position=(0, 0), orientation=math.pi, waveguide_type=waveguide_type)
elems += elements
# fmt: on
return insts, elems, ports
This class definition implements the layout design through the following calls:
library += GratingCoupler()
fp.export_gds(library, file=gds_file)
The simulation defined inside this class can be used for the simulation of the whole circuit.
Run and plot: