Fiber-Array Test Structure
The unbalanced Mach-Zehnder interferometer wired to focusing grating couplers at the 127 µm pitch of a fiber array, with a loopback that calibrates the couplers.
FiberArray from laylight/fiber_array.lyl, compiled in your browserOpen in playgroundA photonic chip is usually measured through grating couplers and a fiber array: a row of fibers held in V-grooves 127 µm apart and polished at an angle, which couples to a row of grating couplers at the same pitch all at once. Every device under test therefore needs waveguides from its ports to couplers on that grid, and a test structure usually adds a loopback, two couplers joined by a plain waveguide. The loopback is used to align the fiber array, and its transmission, the loss of two couplers, is subtracted from the device's.
The example places four focusing grating couplers of Van Laere et al. at a 127 µm pitch, turned so that their access wires point down. The outer two are wired to the input and output of the unbalanced Mach-Zehnder interferometer of Horst et al., 120 µm below the row; the inner two are joined by a loopback that runs 30 µm below the ends of their wires, above the interferometer. All routes are 500 nm wires whose corners are circular bends of 10 µm radius. It is the smallest example of routing: three routes, each computed from the two ports it joins.
References
- F. Van Laere et al., "Compact focusing grating couplers for silicon-on-insulator integrated circuits," IEEE Photonics Technology Letters 19(23), 1919–1921 (2007). doi:10.1109/LPT.2007.908762
- F. Horst et al., "Cascaded Mach-Zehnder wavelength filters in silicon photonics for low loss and flat pass-band WDM (de-)multiplexing," Optics Express 21(10), 11652–11658 (2013). doi:10.1364/OE.21.011652
Parameters
| Parameter | Value | Source |
|---|---|---|
| Couplers | focusing gratings with 500 nm access wires | Van Laere 2007 (see Grating Couplers) |
| Device | unbalanced MZI, ΔL = 20.65 µm | Horst 2013 (see Mach-Zehnder Interferometers) |
| Coupler pitch | 127 µm | the standard pitch of fiber arrays |
| Routes | 500 nm wires, circular bends of 10 µm radius | assumed |
| Layout | MZI axis 120 µm below the couplers' origins, its input half a pitch to the right of the first coupler; loopback 30 µm below the ends of the couplers' wires | assumed |
How it is written
The routes are drawn with a cross-section, wire: a 500 nm core on si that may not bend tighter than 10 µm. A loop places the four couplers and collects their ports in a list, and the interferometer's ports are read from its instance. The coupler and the interferometer gained ports for this example: an output marker on the coupler's access wire and input and output markers on the interferometer, which do not change their layouts.
Each route is then a path drawn with wire through corners taken from the two ports it joins, and bend_radius turns every corner into a bend: an L from the first coupler down and across to the interferometer's input, an L from its output across and up to the last coupler, and a U for the loopback. Because every corner is computed from a port, the routes follow when the interferometer moves or the pitch changes, and because they are drawn with wire, every bend is checked against its minimum radius.
gdsfactory's route_single finds the two L-shaped routes by itself and takes the two corners of the loopback as waypoints. gdstk's FlexPath with bend_radius and KLayout's DPath.round_corners draw the same corners as Laylight, with the positions of the ports worked out from the couplers' wire length and the interferometer's size. Laylight's route statement, which is being designed, will find routes like these from the ports alone; see Auto Routing.
Implementations
| Implementation | Lines of code | Tokens | Layout |
|---|---|---|---|
| Laylight | 19 | 232 | reference |
| gdsfactory | 23 (1.21×) | 259 (1.12×) | same within 3 nm |
| gdstk | 21 (1.11×) | 253 (1.09×) | same within 2 nm |
| KLayout | 24 (1.26×) | 282 (1.22×) | same within 3 nm |
import "grating_couplers.lyl"
import "mzi.lyl"
# The unbalanced MZI (mzi.lyl) wired to focusing grating couplers
# (grating_couplers.lyl) at the 127 um pitch of a fiber array, with a loopback
# between the inner two couplers that calibrates their loss.
define xsection wire(width = 500nm) {
si width
with min_radius = 10um
}
# Every route is a path through corners taken from the ports it joins.
cell FiberArray(pitch = 127um, radius = 10um, loop_depth = 30um, depth = 120um) {
define gcs: [point] = []
for i in 0..4 {
place FocusingGrating at (i * pitch, 0) rotate 90deg as gc
gcs.append(gc.marker.output[0])
}
place MZI at (pitch / 2, -depth) as mzi
define a = mzi.marker.input[0]
define b = mzi.marker.output[0]
define y = gcs[1].y - loop_depth
for corners in [
[gcs[0], (gcs[0].x, a.y), a],
[b, (gcs[3].x, b.y), gcs[3]],
[gcs[1], (gcs[1].x, y), (gcs[2].x, y), gcs[2]],
] {
shape path with corners, wire, bend_radius = radius as wg at self.origin
}
}
"""The unbalanced MZI wired to grating couplers at the pitch of a fiber array,
with a loopback, as laylight/fiber_array.lyl."""
import gdsfactory as gf
from grating_couplers import focusing_grating
from mzi import mzi
from tech import SI
@gf.cell
def fiber_array(pitch=127, radius=10, loop_depth=30, depth=120):
"""The MZI between the outer two of four grating couplers, `pitch` apart,
and a loopback `loop_depth` deep between the inner two."""
c = gf.Component()
wire = gf.cross_section.cross_section(width=0.5, layer=SI, radius_min=10)
gcs = []
for i in range(4):
gc = c << focusing_grating()
gc.drotate(90)
gc.dmove((i * pitch, 0))
gcs.append(gc.ports["o1"])
device = c << mzi()
device.dmove((pitch / 2, -depth))
route = dict(cross_section=wire, bend="bend_circular", radius=radius)
gf.routing.route_single(c, gcs[0], device.ports["o1"], **route)
gf.routing.route_single(c, device.ports["o2"], gcs[3], **route)
y = gcs[1].y - loop_depth
loop = [(gcs[1].x, y), (gcs[2].x, y)]
gf.routing.route_single(c, gcs[1], gcs[2], waypoints=loop, **route)
return c
if __name__ == "__main__":
import sys
fiber_array().write_gds(sys.argv[1])
"""The unbalanced MZI wired to grating couplers at the pitch of a fiber array,
with a loopback, as laylight/fiber_array.lyl."""
import gdstk
import numpy as np
from grating_couplers import focusing_grating
from mzi import mzi
from tech import SI
def fiber_array(pitch=127, radius=10, loop_depth=30, depth=120):
"""The MZI between the outer two of four grating couplers, `pitch` apart,
and a loopback `loop_depth` deep between the inner two."""
cell = gdstk.Cell("FiberArray")
gc, device = focusing_grating(), mzi()
for i in range(4):
cell.add(gdstk.Reference(gc, (i * pitch, 0), rotation=np.pi / 2))
cell.add(gdstk.Reference(device, (pitch / 2, -depth)))
top = -10 # the ends of the couplers' wires
a = (pitch / 2, -depth) # the input and output of the MZI
b = (pitch / 2 + device.bounding_box()[1][0], -depth)
y = top - loop_depth
for points in [
[(0, top), (0, -depth), a],
[b, (3 * pitch, -depth), (3 * pitch, top)],
[(pitch, top), (pitch, y), (2 * pitch, y), (2 * pitch, top)],
]:
cell.add(gdstk.FlexPath(points, 0.5, bend_radius=radius, tolerance=1e-3, **SI))
return cell
if __name__ == "__main__":
import sys
top = fiber_array()
lib = gdstk.Library()
lib.add(top, *top.dependencies(True))
lib.write_gds(sys.argv[1])
"""The unbalanced MZI wired to grating couplers at the pitch of a fiber array,
with a loopback, as laylight/fiber_array.lyl."""
import klayout.db as kdb
from grating_couplers import focusing_grating
from mzi import mzi
from tech import SI, layout
from waveguides import path
def fiber_array(pitch=127, radius=10, loop_depth=30, depth=120):
"""The MZI between the outer two of four grating couplers, `pitch` apart,
and a loopback `loop_depth` deep between the inner two."""
cell = layout.create_cell("FiberArray")
gc, device = focusing_grating(), mzi()
for i in range(4):
trans = kdb.DCplxTrans(1, 90, False, i * pitch, 0)
cell.insert(kdb.DCellInstArray(gc.cell_index(), trans))
trans = kdb.DCplxTrans(pitch / 2, -depth)
cell.insert(kdb.DCellInstArray(device.cell_index(), trans))
top = -10 # the ends of the couplers' wires
a = (pitch / 2, -depth) # the input and output of the MZI
b = (pitch / 2 + device.dbbox().right, -depth)
y = top - loop_depth
si = cell.shapes(layout.layer(SI))
for points in [
[(0, top), (0, -depth), a],
[b, (3 * pitch, -depth), (3 * pitch, top)],
[(pitch, top), (pitch, y), (2 * pitch, y), (2 * pitch, top)],
]:
si.insert(path(points, 0.5).round_corners(radius, 500, 0.0005))
return cell
if __name__ == "__main__":
import sys
fiber_array()
layout.write(sys.argv[1])
III-V-on-Silicon DFB Laser
A heterogeneously integrated III-V/silicon distributed feedback laser with a quarter-wave-shifted grating and adiabatic tapers.
Optical Phased Array
A 16-channel optical phased array after Doylend et al.; a tree of MMIs, heaters 100 µm apart and a routed fan-in onto grating emitters 3.5 µm apart.