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. .. image:: ../example_image/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. .. image:: ../example_image/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. .. image:: ../example_image/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. .. image:: ../example_image/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: .. image:: ../example_image/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. .. image:: ../example_image/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. .. image:: ../example_image/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. .. image:: ../example_image/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``. .. image:: ../example_image/9.11.png