Grating Coupler with linecap
This grating coupler design is used to avoid DRC error at places where turning angle < 90 degree.
Full script
import math from fnpcell import all as fp from typing_extensions import Tuple, List, cast from gpdk import all as pdk from gpdk.technology import get_technology from gpdk.technology.wg.types import CoreCladdingWaveguideType class GC_linecap(fp.PCell): 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, default=fp.USE_DEFAULT_FACTORY) port_names: fp.IPortOptions = fp.PortOptionsParam(count=2, default=("op_0", "optfiber")) 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() 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] = [ fp.el.EllipticalRing(outer_radius=(length, length / ellipse_ratio), layer=waveguide_layer, initial_degrees=-half_degrees, final_degrees=half_degrees)] final_tooth_radius = length for _ in range(teeth): final_tooth_radius = final_tooth_radius + etch_width + tooth_width curve = fp.g.Arc(radius=final_tooth_radius - tooth_width / 2, initial_degrees=-half_degrees, final_degrees=half_degrees) content.append(fp.el.Curve(curve, stroke_width=tooth_width, layer=TECH.LAYER.FWG_COR, line_cap=(fp.el.LineCapRound(), fp.el.LineCapRound()))) triangle_h = (waveguide_width / 2.0) / math.tan(half_angle) triangle = fp.el.Polygon(raw_shape=[(0, 0), (triangle_h, waveguide_width / 2.0), (triangle_h, - waveguide_width / 2.0)], layer=waveguide_layer) content = list(fp.el.PolygonSet(content, layer=waveguide_layer) - triangle) 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] 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 - triangle_h, stroke_width=waveguide_cladding, final_stroke_width=math.tan(half_angle) * cladding_x * 2 + waveguide_cladding, layer=cladding_layer).translated(triangle_h, 0) cladding_polygon &= trapezoid content.append(trapezoid) rec_end = fp.el.Rect(width=2, height=math.tan(half_angle) * cladding_x * 2 + waveguide_cladding, layer=cladding_layer, center=(cladding_x + 1, 0)) content.append(rec_end) # 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)), ] ) elems += elements s = pdk.Straight(length=5, waveguide_type=waveguide_type) s_left = fp.place(s, "op_1", at=(triangle_h, 0)) insts += s_left ports += s_left["op_0"].with_name(port_names[0]) ports += fp.Port(name=port_names[1], position=(fiber_pin_x + fiber_pin_width / 2, 0), orientation=0, shape=fp.g.Rect(width=fiber_pin_width, height=fiber_pin_width, center=(fiber_pin_x + fiber_pin_width / 2, 0)), waveguide_type=waveguide_type) return insts, elems, ports
Section Script Description
User-defined parameters:
length: float = fp.PositiveFloatParam(default=25.0) # Length of the grating taper half_degrees: float = fp.DegreeParam(default=20) # Angle of the grating taper ellipse_ratio: float = fp.PositiveFloatParam(default=1.0, min=1.0, doc="Ellipse(Major/Minor)") # The aspect ratio of the ellipse tooth_width: float = fp.PositiveFloatParam(default=0.5) # Width of the grating etch_width: float = fp.PositiveFloatParam(default=0.5) # Spacing of the grating teeth: int = fp.IntParam(default=30, min=0, doc="Number of tooth") # Number of grating
Layout added in the build method:
Create content list and generate grating sector:
Define a list called content, filled with a circular sector generated with
fp.el.EllipticalRingwith radiuslengthand angle[-half_degrees, half_degrees].content: List[fp.IPolygon] = [fp.el.EllipticalRing(outer_radius=(length, length / ellipse_ratio), layer=waveguide_layer,initial_degrees=-half_degrees, final_degrees=half_degrees)]
Generate grating tooth and capped line on the edge of the tooth
Generate a curve with
fp.g.Arc, pass it intofp.el.Curveto draw a grating along the curve with a width oftooth_widthand a rounded *line_cap*. *Line_cap* can use roundedfp.el.LineCapRound()or triangularfp.el.LineCapTriangle(ratio=0.3), whereratiois the ratio of the height and base of the triangle, so that if ratio<0.5, then the top angle of the line_cap is >90°. (Generating the triangle linecap requires fewer points and runs faster.)final_tooth_radius = length for _ in range(teeth): final_tooth_radius = final_tooth_radius + etch_width + tooth_width curve = fp.g.Arc(radius=final_tooth_radius - tooth_width / 2, initial_degrees=-half_degrees, final_degrees=half_degrees) content.append(fp.el.Curve(curve, stroke_width=tooth_width, layer=TECH.LAYER.FWG_COR, line_cap=(fp.el.LineCapRound(), fp.el.LineCapRound())))
Trim the input port to connect with waveguide
Generate an equilateral triangle with height
triangle_hand basewaveguide_widthusingfp.el.Polygon, do a boolean operation-on the triangle and the sector, and truncate the top corner of the left side of the sector for subsequent connection of a straight waveguide here.triangle_h = (waveguide_width / 2.0) / math.tan(half_angle) triangle = fp.el.Polygon(raw_shape=[(0, 0), (triangle_h, waveguide_width / 2.0), (triangle_h, - waveguide_width / 2.0)], layer=waveguide_layer) content = list(fp.el.PolygonSet(content, layer=waveguide_layer) - triangle)
Add cladding layer
Add trapezoidal cladding.
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 - triangle_h, stroke_width=waveguide_cladding, final_stroke_width=math.tan(half_angle) * cladding_x * 2 + waveguide_cladding, layer=cladding_layer).translated(triangle_h, 0) cladding_polygon &= trapezoid content.append(trapezoid)
Add straight waveguide for connection and left rectangle to avoid DRC error
Add rectangles on the right to avoid having <90° bends and straight waveguides on the left to connect to other devices.
rec_end = fp.el.Rect(width=2, height=math.tan(half_angle) * cladding_x * 2 + waveguide_cladding, layer=cladding_layer, center=(cladding_x + 1, 0)) content.append(rec_end) s = pdk.Straight(length=5, waveguide_type=waveguide_type) s_left = fp.place(s, "op_1", at=(triangle_h, 0)) insts += s_left
Define the ports of the line capped grating coupler
ports += s_left["op_0"].with_name(port_names[0]) ports += fp.Port(name=port_names[1], position=(fiber_pin_x + fiber_pin_width / 2, 0), orientation=0, shape=fp.g.Rect(width=fiber_pin_width, height=fiber_pin_width, center=(fiber_pin_x + fiber_pin_width / 2, 0)), waveguide_type=waveguide_type)
Run the script and view the layout
Run grating_coupler_linecap.py and use layout tool e.g. KLayout to view the generated GDS file, which should be saved under gpdk > components > grating_coupler > local.