Simplify layout by fp.use_sketch_view

In this section, we will introduce the function fp.use_sketch_view to help users become more efficiently when designing layouts. In a complicate photonic circuit, often building up with tens or hundreds of components, it is easy to face the situation that we need huge amount of time to generate GDS file but only want to see a small part of change in the circuit. The real case is that when implementing the waypoints or waylines between two components, we don’t even care about what the components really looks like but focus on the connection between the ports.

The function fp.use_sketch_view captures the geometry of every layer of the cell and simplify them into one single layer. During the first time of generating GDS file opening fp.use_sketch_view, PhotoCAD will automatically generate a sketch-view GDS file and JSon file indicating ports and layers information of the cell. After that, the formation of the GDS file of the circuit will based on the sketch-view GDS file and the non-sketch view cells.

Here is an example of using fp.use_sketch_view in a MZM circuit, containing three MZIs with pn phase shifter on the top arm. The three MZIs are totally different and are all GDS imported cell.

  • mzm_l_200_pn_25 (length difference between two arms: 200 um, pn junction of the phase shifter: 25um)

    • y_splitter_taper_1

    • y_combiner_taper_1

    • phaseshifter_pn25

    ../_images/mzm_l_200_pn_25.png
  • mzm_l_300_pn_50 (length difference between two arms: 300 um, pn junction of the phase shifter: 50um)

    • y_splitter_taper_5

    • y_combiner_taper_5

    • phaseshifter_pn50

    ../_images/mzm_l_300_pn_50.png
  • mzm_l_400_pn_75 (length difference between two arms: 400 um, pn junction of the phase shifter: 75um)

    • y_splitter_taper_10

    • y_combiner_taper_10

    • phaseshifter_pn75

    ../_images/mzm_l_400_pn_75.png
  • Connect three MZIs

    ../_images/topcircuit.png

Implement fp.use_sketch_view

fp.use_sketch_view(
        cell_to_be_sketched,
        conf = {
                    sketch_layer, # layer to be assigned to the sketched cell
                    marker_layer, # layer to be assigned to the marker(arrow) layer
                    marker_width, # width of the arrow
                    marker_length, # length of the arrow
                    silhouette_mode, # Boolean parameter. Default to be False, if True, the sketched cell will generate a boundary indicating the look of the true cell

                }
        )

The configuration parameter sets the sketched cell to the designated layer. The example code above will assign the sketched cell to TEXT_NOTE layer, and by the marker_layer will show the direction of the ports with an arrow.

  • sketch_layer

    ../_images/topcircuit1.png
  • marker_layer

    ../_images/topcircuit2.png
  • silhouette_mode=False(default)

    ../_images/silhouette_modeFalse.png
  • silhouette_mode=True

    ../_images/silhouette_modeTrue.png
  • fp.use_sketch_view(mzm_l_200_pn_25, conf=conf)

    The first parameter of fp.use_sketch_view reads the cell which will be sketched. It could be any level of the circuit.

Note

The fp.use_sketch_view function has to be assign before adding circuit to library, e.g. library += Circuit(). Otherwise the setting will not change when exporting GDS file fp.export_gds.

gds_file = local_output_file(__file__).with_suffix(".gds") # assign the directory for exporting GDS file
library = fp.Library() # generate library for adding PCells, circuits

TECH = get_technology() # call PDK technology setting
conf = fp.SketchConf(sketch_layer=TECH.LAYER.TEXT_NOTE, marker_layer=TECH.LAYER.FLYLINE_MARK) # Set sketch view layers
fp.use_sketch_view(PnPhaseShifter, conf=conf) # Load sketch view function

library += mzm_l_400_pn_75() # Add PCells or circuits to library

fp.export_gds(library, file=gds_file) # Export contents in library to GDS file

Different usage of fp.use_sketch_view

  • Sketch all MZMs

    fp.use_sketch_view(mzm_l_200_pn_25, conf=conf)
    fp.use_sketch_view(mzm_l_300_pn_50, conf=conf)
    fp.use_sketch_view(mzm_l_400_pn_75, conf=conf)
    
    ../_images/topcircuit_mzm.png
  • Sketch all Phase Shifters

    fp.use_sketch_view(phaseshifter_pn25, conf=conf)
    fp.use_sketch_view(phaseshifter_pn50, conf=conf)
    fp.use_sketch_view(phaseshifter_pn75, conf=conf)
    
    ../_images/topcircuit_ps.png
  • Sketch all Combiners

    fp.use_sketch_view(y_combiner_taper1, conf=conf)
    fp.use_sketch_view(y_combiner_taper5, conf=conf)
    fp.use_sketch_view(y_combiner_taper10, conf=conf)
    
    ../_images/topcircuit_combiner.png
  • Sketch only the first MZM

    fp.use_sketch_view(mzm_l_200_pn_25, conf=conf)
    
    ../_images/topcircuit_mzmonly1.png

GDS file build-time results

We track the build-up time of the GDS file when implementing different scenarios.

  • Circuit without any sketch view: 0.1482s

  • 1st time open all MZMs sketch view: 0.1423s

  • 2nd time open all MZMs sketch view: 0.0654s

  • Close all sketch view: 0.1529s

  • 3rd time open all MZMs sketch view: 0.0659s

  • 1st time open child cell (all phase shifters) sketch view: 0.1594s

  • 2nd time open child cell (all phase shifters) sketch view: 0.1385s

  • Close all child cell sketch view: 0.1483s

From the above results we can see that fp.use_sketch_view increases two to three times the speed of generating the GDS file. First time opening the sketch view needs some time to generate the GDS and Json files of the sketched cell, but after that the build-up time can be efficiently saved.

Example Scripts

Here we only show the script of the top circuit of the above example.

class Topcircuit(fp.PCell, locked=True):
    def build(self) -> Tuple[fp.InstanceSet, fp.ElementSet, fp.PortSet]:
        insts, elems, ports = super().build()
        TECH = get_technology()

        mzm1 = mzm_l_200_pn_25() # Call and place the three child mzms
        mzm2 = mzm_l_300_pn_50().translated(200, 200)
        mzm3 = mzm_l_400_pn_75().translated(500, 0)

        link = fp.create_links( # Link the three child mzms
            link_type=TECH.WG.FWG.C.WIRE,
            bend_factory=TECH.WG.FWG.C.WIRE.BEND_CIRCULAR,
            specs=[
                fp.LinkBetween(
                    end=mzm2["op_0"],
                    start=mzm1["op_1"]
                ),
                fp.LinkBetween(
                    start=mzm2["op_1"],
                    end=mzm3["op_0"]
                ),

            ]
        )

        insts += mzm1
        insts += mzm2
        insts += mzm3
        insts += link

        return insts, elems, ports


if __name__ == "__main__":
    import sys
    from time import perf_counter
    from gpdk.util.path import local_output_file

    gds_file = local_output_file(__file__).with_suffix(".gds")
    library = fp.Library()

    TECH = get_technology()
    conf = fp.SketchConf(sketch_layer=TECH.LAYER.TEXT_NOTE, marker_layer=TECH.LAYER.FLYLINE_MARK)

    def test_build(tag: str): # Create a build test function to count the time to build up GDS file in different situations.
        start_time = perf_counter()
        library = fp.Library()
        library += Topcircuit()
        fp.export_gds(library, file=gds_file.with_suffix(f".{tag}.gds"))
        print(f"{tag} view elapsed time: {perf_counter()-start_time:.4f}\n")

    tag = sys.argv[1] if len(sys.argv) == 2 else "test"
    if tag.startswith("original"):
        test_build(tag)
    elif tag.startswith("mzm"):
        fp.use_sketch_view(mzm_l_200_pn_25, conf=conf) # Assign fp.use_sketch_view function before test_build function
        fp.use_sketch_view(mzm_l_300_pn_50, conf=conf)
        fp.use_sketch_view(mzm_l_400_pn_75, conf=conf)
        test_build(tag)
    elif tag.startswith("ps"):
        fp.use_sketch_view(phaseshifter_pn25, conf=conf) # Assign fp.use_sketch_view function before test_build function
        fp.use_sketch_view(phaseshifter_pn50, conf=conf)
        fp.use_sketch_view(phaseshifter_pn75, conf=conf)
        test_build(tag)
    elif tag.startswith("combiner"):
        fp.use_sketch_view(y_combiner_taper1, conf=conf) # Assign fp.use_sketch_view function before test_build function
        fp.use_sketch_view(y_combiner_taper5, conf=conf)
        fp.use_sketch_view(y_combiner_taper10, conf=conf)
        test_build(tag)
    elif tag.startswith("test"):


        import os
        import subprocess

        # Test 1 : close sketch view
        subprocess.run([sys.executable, sys.argv[0], "original1"], env=os.environ)
        # Test 2 : open sketch view 1
        subprocess.run([sys.executable, sys.argv[0], "mzml"], env=os.environ)
        # Test 3 : open sketch view 2
        subprocess.run([sys.executable, sys.argv[0], "mzm2"], env=os.environ)
        # Test 4 : close sketch view
        subprocess.run([sys .executable, sys.argv[0],"original2"], env=os.environ )
        # Test 5 : open sketch view 3
        subprocess.run([sys.executable, sys.argv[0],"mzm3"], env=os.environ)
        # Test 6 : open child cell sketch 1 view
        subprocess.run([sys.executable, sys.argv[0], "ps1"], env=os.environ)
        # Test 7 : open child cell sketch 2 view:
        subprocess.run([sys.executable, sys.argv[0], "ps2"], env=os.environ)
        # Test 8 : close child cell sketch view
        subprocess.run([sys.executable, sys.argv[0], "original3"], env=os.environ)
        # Test 9 : open sketch view phase shifter 3
        subprocess.run([sys.executable, sys.argv[0],"ps3"], env=os.environ)
        # Test 9 : open sketch view combiner
        subprocess.run([sys.executable, sys.argv[0],"combiner"], env=os.environ)