Add-Drop Filter Layout¶
This notebook demonstrates how to create a basic add-drop filter layout using PhotonForge. The goal is to focus on layout generation without involving simulation or technology setup.
PhotonForge is a powerful tool for creating photonic layouts. This example will guide you through the process of creating an add-drop filter with one input grating and three output gratings (through, drop, and isolation ports).
Import Libraries¶
We start by importing the necessary PhotonForge library. We also use basic technology, although it is not necessary to use a technology when you aim to only create a layout.
It is also good (but not necessary) to start a LiveViewer instance at the top of the notebook and use it whenever we create a structure or change a component.
[1]:
import photonforge as pf
from photonforge.live_viewer import LiveViewer
pf.config.default_technology = pf.basic_technology()
viewer = LiveViewer()
LiveViewer started at http://localhost:34485
Define Design Parameters¶
Before building the layout, we define the key design parameters that control the geometry and spacing of the components:
Grating Distance – Distance between the starting points of the input and output gratings.
Output Grating Spacing – Vertical distance between the output gratings.
Waveguide Width – Width of the optical waveguide.
Ring Radius – Radius of the ring resonator.
Gap – Distance between the bus waveguide and the ring resonator.
Grating Taper Length – Length of the taper section of the grating.
These parameters will allow us to control the size and alignment of the layout.
[2]:
# design parameters
grating_distance = 200
output_grating_spacing = 50
waveguide_width = 1.0
ring_radius = 10
gap = 1.0
grating_taper_length = 30
Create Main Component¶
We create a main component that will serve as the container for the entire layout. All individual elements (gratings, waveguides, and the ring) will be added to this main component.
The Component object in PhotonForge allows us to group multiple elements into a single design.
[3]:
main = pf.Component("ADD_DROP_FILTER")
Create Grating Structure¶
Next, we create a grating structure that will be used for both the input and output ports.
The stencil.grating function generates a grating structure with a defined period, number of periods, and taper. Tapering is important to reduce optical losses when coupling light into the waveguides. The output is a list of sructures that we need to add them to the grating component. All structures are placed on layer (1, 0), which corresponds to the GDS layer and datatype. Assigning structures to a specific layer
is essential for exporting the design to a GDS file.
[4]:
grating = pf.Component("GRATING")
grating_structure = pf.stencil.grating(
period=0.6,
num_periods=20,
width=10,
taper_length=grating_taper_length,
taper_width=waveguide_width,
)
# add grating structure to grating component
grating.add((1, 0), *grating_structure)
viewer(grating)
[4]:
Place Input and Output Gratings¶
With the grating structure prepared, we now position the gratings at specified locations within the main component:
The input grating is placed at the origin and rotated by 180 degrees.
The through-port grating is horizontally aligned with the input grating.
The drop-port and isolation-port gratings are placed vertically above the through-port at defined distances.
This configuration ensures proper alignment of optical paths for efficient coupling to fiber arrays.
We utilize the Reference class to create a Reference to the previously defined grating Component. This approach allows efficient reuse of the same component at multiple locations within the layout.
[5]:
input_grating = pf.Reference(grating, (0, 0), rotation=180)
output_grating_through = pf.Reference(
grating, (grating_distance - 2 * grating_taper_length, 0)
)
output_grating_isolation = pf.Reference(
grating, (grating_distance - 2 * grating_taper_length, output_grating_spacing)
)
output_grating_drop = pf.Reference(
grating, (grating_distance - 2 * grating_taper_length, 2 * output_grating_spacing)
)
main.add(
(1, 0),
input_grating,
output_grating_through,
output_grating_isolation,
output_grating_drop,
)
[5]:
Create Bus Waveguide¶
The bus waveguide connects the input grating to the through port.
We extract the bounds of the input and through port gratings to define the start and end points of the waveguide.
The Path.segment function creates a straight waveguide between two points.
This forms the main optical path for light propagation:
[6]:
(min_x_in, min_y_in), (max_x_in, max_y_in) = input_grating.bounds()
(min_x_out1, min_y_out1), (max_x_out1, max_y_out1) = output_grating_through.bounds()
bus_waveguide = pf.Path(
origin=(max_x_in, (max_y_in + min_y_in) / 2), width=waveguide_width
).segment((min_x_out1, (max_y_out1 + min_y_out1) / 2))
viewer(bus_waveguide)
[6]:
Create Ring Resonator¶
We now create the ring resonator:
The ring is centered along the bus waveguide.
The Circle function defines the ring with appropriately identifying
radiusandinner_radius.
[7]:
x_ring = (max_x_in + min_x_out1) / 2
y_ring = (max_y_in + min_y_in) / 2 + gap + waveguide_width
ring_resonator = pf.Circle(
radius=ring_radius + waveguide_width / 2,
inner_radius=ring_radius - waveguide_width / 2,
center=(x_ring, y_ring + ring_radius),
)
viewer(ring_resonator)
[7]:
Create Isolation-Drop Path¶
We now define the path that couples light from the ring resonator to the isolation and drop ports:
A series of straight segments and bends route the waveguide through the layout.
The s_bend function creates smooth transitions to minimize optical loss.
[8]:
(min_x_out3, min_y_out3), (max_x_out3, max_y_out3) = output_grating_drop.bounds()
(min_x_out2, min_y_out2), (max_x_out2, max_y_out2) = output_grating_isolation.bounds()
isolation_drop_path = (
pf.Path(origin=(min_x_out3, (max_y_out3 + min_y_out3) / 2), width=waveguide_width)
.segment((max_x_in, (max_y_out3 + min_y_out3) / 2))
.arc(90, 270, output_grating_spacing / 2)
.s_bend((x_ring - ring_radius, y_ring + 2 * ring_radius + gap + waveguide_width))
.segment((x_ring + ring_radius, y_ring + 2 * ring_radius + gap + waveguide_width))
.s_bend((min_x_out2, (max_y_out2 + min_y_out2) / 2))
)
viewer(isolation_drop_path)
[8]:
Export final GDS file¶
Add the bus waveguide, ring resonator, and isolation-drop path to the main component and export the layout to a GDS file.
[9]:
main.add((1, 0), bus_waveguide, ring_resonator, isolation_drop_path)
main.write_gds("add_drop_filter.gds")
[9]: