Ring filter

Full script

import math
from typing import Tuple
from fnpcell import all as fp
from gpdk.technology import get_technology
from gpdk.technology.interfaces import CoreCladdingWaveguideType

@fp.pcell_class()

class RingFilter(fp.PCell):
    """
    Attributes:
        ring_radius: defaults to 10
        gap: defaults to 0.2
        gap_monitor: defaults to 0.5
        waveguide_type: type of waveguide
        port_names: defaults to ["op_0", "op_1", "op_2", "op_3", "ep_0", "ep_1"]

    Examples:
    ```python
    TECH = get_technology()
        ring = RingFilter(name="f1", waveguide_type=TECH.WG.FWG.C.WIRE)
    fp.plot(ring)
    ```
    ![RingFilter](images/ring_filter.png)
    """

    ring_radius: float = fp.PositiveFloatParam(default=10).as_field()
    gap: float = fp.PositiveFloatParam(default=0.2).as_field()
    gap_monitor: float = fp.PositiveFloatParam(default=0.5).as_field()
    waveguide_type: CoreCladdingWaveguideType = fp.WaveguideTypeParam(type=CoreCladdingWaveguideType).as_field()
    port_names: fp.IPortOptions = fp.PortOptionsParam(count=6, default=["op_0", "op_1", "op_2", "op_3", "ep_0", "ep_1"]).as_field()

    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

        LAYER = TECH.LAYER
        ring_radius = self.ring_radius
        gap = self.gap
        gap_monitor = self.gap_monitor
        waveguide_type = self.waveguide_type
        port_names = self.port_names

        si_etch2_layer = LAYER.SWG_COR
        vl_layer = LAYER.VIA1_DRW
        mh_layer = LAYER.TIN_DRW
        m1_width = 6.0
        m1_enc = 4.25
        via_width = 5.0
        via_height = 5.0
        taper_big_end = 2.0
        taper_small_end = 0.3
        taper_length = 5.0  # FWG
        w_mh = 2.0
        min_mh_degrees = -75.0
        max_mh_degrees = 255.0

        w_m1_out = m1_width
        r_m1_out = ring_radius + m1_enc
        r_mh = ring_radius
        r_mh_in = r_mh - w_mh / 2

        w_si_3 = w_m1_out + r_m1_out * 2
        h_si_3 = w_si_3

        core_width = waveguide_type.core_width

        x0 = 0
        y0 = ring_radius + gap + core_width

        ring = waveguide_type(curve=fp.g.EllipticalArc(radius=ring_radius, origin=(x0, y0))).with_ports((None, None)).with_name("ring")
        insts += ring

        bus_length = w_si_3 + taper_length * 2
        bus = waveguide_type(curve=fp.g.Line(length=bus_length, anchor=fp.Anchor.CENTER, origin=(0, 0))).with_name("bus")
        insts += bus
        y_offset = ring_radius * 2 + gap + gap_monitor + core_width * 2
        monitor = waveguide_type(curve=fp.g.Line(length=bus_length, anchor=fp.Anchor.CENTER, origin=(0, y_offset))).with_name("monitor")
        insts += monitor
        rect = fp.el.Rect(width=w_si_3, height=h_si_3, layer=si_etch2_layer, origin=(x0, y0))
        elems += rect

        taper2 = fp.el.Line(length=taper_length, stroke_width=taper_big_end, final_stroke_width=taper_small_end, layer=si_etch2_layer, origin=(x0 + w_si_3 / 2, 0))
        elems += taper2
        taper3 = fp.el.Line(length=taper_length, stroke_width=taper_big_end, final_stroke_width=taper_small_end, layer=si_etch2_layer, origin=(x0 + w_si_3 / 2, y_offset))
        elems += taper3
        taper0 = fp.el.Line(length=taper_length, stroke_width=taper_small_end, final_stroke_width=taper_big_end, layer=si_etch2_layer, anchor=fp.Anchor.END, origin=(x0 - w_si_3 / 2, y_offset))
        elems += taper0
        taper1 = fp.el.Line(length=taper_length, stroke_width=taper_small_end, final_stroke_width=taper_big_end, layer=si_etch2_layer, anchor=fp.Anchor.END, origin=(x0 - w_si_3 / 2, 0))
        elems += taper1
        ring_mh = fp.el.EllipticalArc(radius=r_mh, stroke_width=w_mh, layer=mh_layer, final_degrees=max_mh_degrees - min_mh_degrees, transform=fp.rotate(degrees=min_mh_degrees).translate(x0, y0))
        elems += ring_mh

        min_mh_radians = math.radians(min_mh_degrees)
        print(min_mh_degrees)
        dx = r_mh_in * math.cos(min_mh_radians)
        dy = r_mh_in * math.sin(min_mh_radians)

        # TODO magic number 10
        #VIA1 Layer
        v1 = fp.el.Rect(width=via_width, height=via_height, layer=vl_layer, origin=(dx + via_width, -10 + via_height / 2 - core_width / 2))
        elems += v1
        v2 = fp.el.Rect(width=via_width, height=via_height, layer=vl_layer, origin=(-dx - via_width, -10 + via_height / 2 - core_width / 2))
        elems += v2
        #M2 Layer
        m2 = fp.el.Rect(width=10, height=10, layer=LAYER.M2_DRW, origin=(dx + via_width, -10 + via_height / 2 - core_width / 2))
        elems += m2
        m2 = fp.el.Rect(width=10, height=10, layer=LAYER.M2_DRW, origin=(-dx - via_width, -10 + via_height / 2 - core_width / 2))
        elems += m2
        #VIA2 Layer
        v1 = fp.el.Rect(width=via_width, height=via_height, layer=LAYER.VIA2_DRW, origin=(dx + via_width, -10 + via_height / 2 - core_width / 2))
        elems += v1
        v2 = fp.el.Rect(width=via_width, height=via_height, layer=LAYER.VIA2_DRW, origin=(-dx - via_width, -10 + via_height / 2 - core_width / 2))
        elems += v2
        #MT Layer
        mt = fp.el.Rect(width=10, height=10, layer=LAYER.MT_DRW, origin=(dx + via_width, -10 + via_height / 2 - core_width / 2))
        elems += mt
        mt = fp.el.Rect(width=10, height=10, layer=LAYER.MT_DRW, origin=(-dx - via_width, -10 + via_height / 2 - core_width / 2))
        elems += mt

        h_mh = y0 + dy + core_width / 2 + via_height / 2
        mh1 = fp.el.Rect(width=w_mh, height=h_mh, layer=mh_layer, origin=(dx + w_mh / 2, -core_width / 2 - 2.5 + h_mh / 2))
        elems += mh1
        mh2 = fp.el.Rect(width=w_mh, height=h_mh, layer=mh_layer, origin=(-dx - w_mh / 2, -core_width / 2 - 2.5 + h_mh / 2))
        elems += mh2
        # magic number 10
        mh1b = fp.el.Rect(width=10, height=10, layer=mh_layer, origin=(-dx - 5, -core_width / 2 - via_height / 2 - 10 / 2))
        elems += mh1b
        mh2b = fp.el.Rect(width=10, height=10, layer=mh_layer, origin=(dx + 5, -core_width / 2 - via_height / 2 - 10 / 2))
        elems += mh2b

        top_start_ray, top_end_ray = monitor.curve.end_rays
        bottom_start_ray, bottom_end_ray = bus.curve.end_rays

        pin1_x, pin1_y = (-dx - via_width, -10 + via_height / 2 - core_width / 2)
        pin2_x, pin2_y = (dx + via_width, -10 + via_height / 2 - core_width / 2)
        ports += fp.Port(name=port_names[0], position=top_start_ray.position, orientation=top_start_ray.orientation, waveguide_type=waveguide_type)
        ports += fp.Port(name=port_names[1], position=bottom_start_ray.position, orientation=bottom_start_ray.orientation, waveguide_type=waveguide_type)
        ports += fp.Port(name=port_names[2], position=bottom_end_ray.position, orientation=bottom_end_ray.orientation, waveguide_type=waveguide_type)
        ports += fp.Port(name=port_names[3], position=top_end_ray.position, orientation=top_end_ray.orientation, waveguide_type=waveguide_type)
        ports += fp.Pin(name=port_names[4], position=(pin1_x, pin1_y), shape=v1.shape, metal_line_type=TECH.METAL.MT.W10)
        ports += fp.Pin(name=port_names[5], position=(pin2_x, pin2_y), shape=v2.shape, metal_line_type=TECH.METAL.MT.W10)

        # fmt: on
        return insts, elems, ports


if __name__ == "__main__":
    from pathlib import Path

    gds_file = Path(__file__).parent / "local" / Path(__file__).with_suffix(".gds").name
    library = fp.Library()

    TECH = get_technology()
    # =============================================================
    # fmt: off

    library += RingFilter()

    # fmt: on
    # =============================================================
    fp.export_gds(library, file=gds_file)
    fp.plot(library)

Run the complete script once, generating the following GDS layout.

../_images/9.1.png

Parameters and testing descriptions

With the preceding examples as a basis, we will mainly test some key parameters later.

# Define three layers
  si_etch2_layer = LAYER.SWG_COR
vl_layer = LAYER.VIA1_DRW
mh_layer = LAYER.TIN_DRW
# Define several basic parameters of the device
m1_width = 6.0
m1_enc = 4.25
via_width = 5.0
via_height = 5.0
taper_big_end = 2.0
taper_small_end = 0.3
taper_length = 5.0  # FWG
w_mh = 2.0
min_mh_degrees = -75.0
max_mh_degrees = 255.0

w_m1_out = m1_width
r_m1_out = ring_radius + m1_enc
r_mh = ring_radius
r_mh_in = r_mh - w_mh / 2

w_si_3 = w_m1_out + r_m1_out * 2
h_si_3 = w_si_3

In the above code, w_si_3 is equal to h_si_3, below we change w_si_3 to :

w_si_3 = ring_radius * 2
h_si_3 = w_si_3

Run to obtain the following layout.

../_images/9.2.png

gap and gap_monitor control the gaps labeled in the figure below, respectively, with gap controlling the bottom and gap_monitor controlling the top.

../_images/9.3.png

The following diagram illustrates the parameters of r_mh_in, w_mh, min_mh_degrees, dx and dy in the program. It should be noted that the value of min_mh_degrees has a positive or negative nature and needs to be taken into account when designing.

../_images/9.5.png

The following code sets the origin of the entire device and adds a ring structure with radius ring_radius to insts, which has no ports structure and is named ring.

x0 = 0
y0 = ring_radius + gap + core_width
ring = waveguide_type(curve=fp.g.EllipticalArc(radius=ring_radius, origin=(x0, y0))).with_ports((None, None)).with_name("ring")
insts += ring

y0 is the radius of the ring + the spacing below + the width of the core, the actual position of the origin of the entire device becomes:

../_images/9.6.png

The following code adds four taper structures to the si_etch2_layer layer. length is the total length of the taper, stroke_width can be interpreted as the width of the left end of the taper, and final_stroke_width is the width of the right end.

taper2 = fp.el.Line(length=taper_length, stroke_width=taper_big_end, final_stroke_width=taper_small_end, layer=si_etch2_layer, origin=(x0 + w_si_3 / 2, 0))
elems += taper2
taper3 = fp.el.Line(length=taper_length, stroke_width=taper_big_end, final_stroke_width=taper_small_end, layer=si_etch2_layer, origin=(x0 + w_si_3 / 2, y_offset))
elems += taper3
taper0 = fp.el.Line(length=taper_length, stroke_width=taper_small_end, final_stroke_width=taper_big_end, layer=si_etch2_layer, anchor=fp.Anchor.END, origin=(x0 - w_si_3 / 2, y_offset))
elems += taper0
taper1 = fp.el.Line(length=taper_length, stroke_width=taper_small_end, final_stroke_width=taper_big_end, layer=si_etch2_layer, anchor=fp.Anchor.END, origin=(x0 - w_si_3 / 2, 0))
elems += taper1
ring_mh = fp.el.EllipticalArc(radius=r_mh, stroke_width=w_mh, layer=mh_layer, final_degrees=max_mh_degrees - min_mh_degrees, transform=fp.rotate(degrees=min_mh_degrees).translate(x0, y0))
elems += ring_mh

Here we remove the anchor parameter from taper1 and run the result compared with the original result, we can see that the taper has moved. Here the user can interpret this as the origin point is at the center of the right end of the taper when anchor=fp.Anchor.END is not added, and if anchor=fp.Anchor.END is added, the origin point is at the center of the left end of the taper.

../_images/9.7.png

The code below is to add structures on four different layers with the same square structure in two groups in the figure below.

../_images/9.9.png
# TODO magic number 10
#VIA1 Layer
v1 = fp.el.Rect(width=via_width, height=via_height, layer=vl_layer, origin=(dx + via_width, -10 + via_height / 2 - core_width / 2))
elems += v1
v2 = fp.el.Rect(width=via_width, height=via_height, layer=vl_layer, origin=(-dx - via_width, -10 + via_height / 2 - core_width / 2))
elems += v2
#M2 Layer
m2 = fp.el.Rect(width=10, height=10, layer=LAYER.M2_DRW, origin=(dx + via_width, -10 + via_height / 2 - core_width / 2))
elems += m2
m2 = fp.el.Rect(width=10, height=10, layer=LAYER.M2_DRW, origin=(-dx - via_width, -10 + via_height / 2 - core_width / 2))
elems += m2
#VIA2 Layer
v1 = fp.el.Rect(width=via_width, height=via_height, layer=LAYER.VIA2_DRW, origin=(dx + via_width, -10 + via_height / 2 - core_width / 2))
elems += v1
v2 = fp.el.Rect(width=via_width, height=via_height, layer=LAYER.VIA2_DRW, origin=(-dx - via_width, -10 + via_height / 2 - core_width / 2))
elems += v2
#MT Layer
mt = fp.el.Rect(width=10, height=10, layer=LAYER.MT_DRW, origin=(dx + via_width, -10 + via_height / 2 - core_width / 2))
elems += mt
mt = fp.el.Rect(width=10, height=10, layer=LAYER.MT_DRW, origin=(-dx - via_width, -10 + via_height / 2 - core_width / 2))
elems += mt

The lower code is responsible for adding the rectangle structure to the mh_layer.

h_mh = y0 + dy + core_width / 2 + via_height / 2
mh1 = fp.el.Rect(width=w_mh, height=h_mh, layer=mh_layer, origin=(dx + w_mh / 2, -core_width / 2 - 2.5 + h_mh / 2))
elems += mh1
mh2 = fp.el.Rect(width=w_mh, height=h_mh, layer=mh_layer, origin=(-dx - w_mh / 2, -core_width / 2 - 2.5 + h_mh / 2))
elems += mh2

Adjust the position of the origin point of the structure so that the lower two positions are aligned.

../_images/9.10.png

The code below is to get the location of the six points where the ports needs to be placed, and add the ports by fp.Port and the pins by fp.Pin. In fp.Pin(), set the pins to the same shape (square) as v2, and set the metal wire type to TECH.METAL.MT.W10.

top_start_ray, top_end_ray = monitor.curve.end_rays
bottom_start_ray, bottom_end_ray = bus.curve.end_rays

pin1_x, pin1_y = (-dx - via_width, -10 + via_height / 2 - core_width / 2)
pin2_x, pin2_y = (dx + via_width, -10 + via_height / 2 - core_width / 2)
ports += fp.Port(name=port_names[0], position=top_start_ray.position, orientation=top_start_ray.orientation, waveguide_type=waveguide_type)
ports += fp.Port(name=port_names[1], position=bottom_start_ray.position, orientation=bottom_start_ray.orientation, waveguide_type=waveguide_type)
ports += fp.Port(name=port_names[2], position=bottom_end_ray.position, orientation=bottom_end_ray.orientation, waveguide_type=waveguide_type)
ports += fp.Port(name=port_names[3], position=top_end_ray.position, orientation=top_end_ray.orientation, waveguide_type=waveguide_type)
ports += fp.Pin(name=port_names[4], position=(pin1_x, pin1_y), shape=v1.shape, metal_line_type=TECH.METAL.MT.W10)
ports += fp.Pin(name=port_names[5], position=(pin2_x, pin2_y), shape=v2.shape, metal_line_type=TECH.METAL.MT.W10)

The following diagram illustrates the location of each port and its corresponding port_names.

../_images/9.11.png