Grating Couplers
Uniform, apodized and focusing grating couplers between a single-mode fiber and silicon waveguides.
GratingCouplers from laylight/grating_couplers.lyl, compiled in your browserOpen in playgroundA grating coupler diffracts light out of the chip surface into an optical fiber held a few degrees off vertical, and back. It is etched into a wide silicon waveguide, about as wide as the fiber's mode, which a taper connects to the single-mode waveguide. The example draws three generations of the design.
The uniform grating of Taillaert et al. is the classic one: 20 trenches, 70 nm deep, with a 630 nm period and a fill factor of 0.5, across a 12 µm wide waveguide, which a 400 µm linear taper narrows to 450 nm. The apodized grating of Bozzola et al. varies the trenches along the grating, so that the light leaves it with a profile closer to the fiber's Gaussian mode: 24 fully etched trenches, whose positions and widths are taken from the paper's table. It is a simulation paper; the grating width and taper are assumed. The focusing grating of Van Laere et al. replaces the long taper by curved trenches that focus the light onto the end of the waveguide: its lines are confocal ellipses with a common focus at the waveguide's end, q λ = n_eff r − x cos θ_c for integers q, in a 37° sector 28 µm long.
References
- D. Taillaert et al., "Grating couplers for coupling between optical fibers and nanophotonic waveguides," Japanese Journal of Applied Physics 45(8A), 6071–6077 (2006). doi:10.1143/JJAP.45.6071
- A. Bozzola, L. Carroll, D. Gerace, I. Cristiani and L. C. Andreani, "Optimising apodized grating couplers in a pure SOI platform to −0.5 dB coupling efficiency," Optics Express 23(12), 16289–16304 (2015). doi:10.1364/OE.23.016289
- 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
Parameters
| Parameter | Value | Source |
|---|---|---|
| Uniform: period, fill factor, etch | 630 nm, 0.5, 70 nm | Taillaert 2006, Fig. 3, Sec. 3.1 |
| Uniform: width, taper | 12 µm; 400 µm linear taper to 450 nm | Taillaert 2006, Sec. 3.2 |
| Uniform: periods | 20 | assumed |
| Apodized: trench positions and widths | 24 trenches, fully etched | Bozzola 2015, Table 1 (220 nm "constrained" design) |
| Apodized: width, taper | 12 µm, the uniform grating's taper | assumed |
| Focusing: period, duty cycle, etch | 630 nm, 50 %, 70 nm | Van Laere 2007 |
| Focusing: fiber angle | 80° to the chip surface | Van Laere 2007 |
| Focusing: n_eff | λ/Λ + cos θ_c = 2.634 at λ = 1550 nm | derived |
| Focusing: lines, sector | q = 20 to 43; 37°, 28 µm | assumed, to fit the paper's 18.5 µm × 28 µm footprint |
How it is written
Each grating is a cell. The uniform grating places its trenches in a loop on the shallow-etch layer. The apodized grating collects its fully etched trenches in a list and subtracts them from the grating region in one statement, slab - trenches. The focusing grating writes the paper's equation almost literally: for each line q it draws the two confocal ellipses that bound a trench, places them as helper shapes with place* (so they are not drawn themselves), and keeps (outer - inner) & sector, the band between them inside the grating's sector. The Python versions compute the same trenches as polygons from the polar form of the equation, since a boolean per trench is more code there.
Implementations
| Implementation | Lines of code | Tokens | Layout |
|---|---|---|---|
| Laylight | 71 | 604 | reference |
| gdsfactory | 78 (1.10×) | 783 (1.30×) | same within 2 nm |
| gdstk | 71 (1.00×) | 745 (1.23×) | same within 2 nm |
| KLayout | 76 (1.07×) | 815 (1.35×) | same within 2 nm |
import "tech.lyl"
# Trench centers (from the first one) and widths of the apodized grating,
# Bozzola et al. 2015, Table 1.
define trench_positions = [
0nm, 712.7nm, 1377nm, 2061.7nm, 2769.7nm, 3429.3nm, 4159nm, 4846.6nm, 5569.2nm,
6250.5nm, 6982.2nm, 7655.1nm, 8351.4nm, 9057.9nm, 9734.4nm, 10452.8nm,
11121.2nm, 11813.3nm, 12512.3nm, 13197.8nm, 13916.8nm, 14583.6nm, 15317nm,
16017.2nm
]
define trench_widths = [
100.3nm, 100.7nm, 130.1nm, 121.3nm, 127.8nm, 150.2nm, 154.8nm, 145.9nm, 164.8nm,
147.8nm, 153.4nm, 150.3nm, 133.3nm, 156.8nm, 162.5nm, 141nm, 164.5nm, 140.9nm,
146.3nm, 130.8nm, 142.2nm, 137.7nm, 114.3nm, 132.7nm
]
# A uniform grating of shallow-etched trenches at the end of a long linear
# taper, after Taillaert et al., Jpn. J. Appl. Phys. 2006.
cell UniformGrating(
period = 630nm,
fill_factor = 0.5,
periods = 20,
width = 12um,
taper_length = 400um,
wire_width = 450nm,
) {
layer si {
shape taper with taper_length, wire_width, width as flare
place flare.end at self.origin
shape rect with periods * period, width as grating.left_center at self.origin
}
layer shallow {
for i in 0..periods {
shape rect with (1 - fill_factor) * period, width as trench
place trench at ((i + 0.5) * period, 0)
}
}
}
# An apodized grating of fully etched trenches, after Bozzola et al., Opt.
# Express 2015, with the taper of the uniform grating.
# `margin` lies before the first and after the last trench center.
cell ApodizedGrating(
width = 12um, taper_length = 400um, wire_width = 450nm, margin = 1um
) {
layer si {
shape taper with taper_length, wire_width, width as flare
define last = trench_positions[len(trench_positions) - 1]
shape rect with last + 2 * margin, width as slab.left_center at self.origin
place flare.end at self.origin
define trenches = []
for i in 0..len(trench_positions) {
shape rect with trench_widths[i], width as trench
place trench at (margin + trench_positions[i], 0)
trenches.append(trench)
}
shape slab - trenches as grating
}
}
# A focusing grating: shallow-etched trenches along confocal ellipses whose
# common focus is the end of the access wire, after Van Laere et al., IEEE
# Photon. Technol. Lett. 2007.
cell FocusingGrating(
period = 630nm,
fill_factor = 0.5,
wavelength = 1550nm,
fiber_angle = 80deg, # between the fiber and the chip surface
first_order = 20, # q of the first line
lines = 24,
opening = 37deg,
length = 28um,
wire_width = 500nm,
wire_length = 10um,
) {
marker { output { at wire.end } }
layer si {
shape circle with length, angle = opening, start_angle = -opening / 2 as sector
place sector at self.origin
shape straight with wire_length, wire_width as wire
place wire.start at self.origin rotate 180deg
}
# Line q is q * wavelength = n_eff * r - cos(fiber_angle) * x: an ellipse
# with a focus at the origin, eccentricity e and semi-axes q * a, q * b.
layer shallow {
define n_eff = wavelength / period + cos(fiber_angle)
define e = cos(fiber_angle) / n_eff
define a = wavelength / n_eff / (1 - e * e)
define b = a * sqrt(1 - e * e)
for q in first_order..first_order + lines {
define q2 = q + 1 - fill_factor
shape ellipse with q * a, q * b as inner
shape ellipse with q2 * a, q2 * b as outer
place* inner at (q * a * e, 0)
place* outer at (q2 * a * e, 0)
shape (outer - inner) & sector as trench
}
}
}
# The three couplers at the pitch of a fiber array.
cell GratingCouplers(pitch = 127um) {
place UniformGrating at self.origin
place ApodizedGrating at (0, -pitch)
place FocusingGrating at (0, -2 * pitch)
}
"""Uniform, apodized and focusing grating couplers, as
laylight/grating_couplers.lyl."""
import gdsfactory as gf
import numpy as np
from tech import SHALLOW, SI
# Trench centers (from the first one) and widths of the apodized grating in nm,
# Bozzola et al. 2015, Table 1.
# fmt: off
TRENCH_POSITIONS = np.array([
0, 712.7, 1377, 2061.7, 2769.7, 3429.3, 4159, 4846.6, 5569.2, 6250.5, 6982.2,
7655.1, 8351.4, 9057.9, 9734.4, 10452.8, 11121.2, 11813.3, 12512.3, 13197.8,
13916.8, 14583.6, 15317, 16017.2,
]) / 1000
TRENCH_WIDTHS = np.array([
100.3, 100.7, 130.1, 121.3, 127.8, 150.2, 154.8, 145.9, 164.8, 147.8, 153.4,
150.3, 133.3, 156.8, 162.5, 141, 164.5, 140.9, 146.3, 130.8, 142.2, 137.7,
114.3, 132.7,
]) / 1000
# fmt: on
@gf.cell
def uniform_grating(
period=0.63,
fill_factor=0.5,
periods=20,
width=12,
taper_length=400,
wire_width=0.45,
):
"""A uniform grating of shallow-etched trenches at the end of a long linear
taper, after Taillaert et al., Jpn. J. Appl. Phys. 2006."""
c = gf.Component()
flare = c << gf.components.taper(taper_length, wire_width, width, layer=SI)
flare.dmovex(-taper_length)
grating = c << gf.components.rectangle((periods * period, width), layer=SI)
grating.dmovey(-width / 2)
trench = gf.components.rectangle(
((1 - fill_factor) * period, width), layer=SHALLOW, centered=True
)
for i in range(periods):
(c << trench).dmovex((i + 0.5) * period)
return c
@gf.cell
def apodized_grating(width=12, taper_length=400, wire_width=0.45, margin=1):
"""An apodized grating of fully etched trenches, after Bozzola et al., Opt.
Express 2015, with the taper of the uniform grating. `margin`: silicon
before the first and after the last trench center."""
c = gf.Component()
flare = c << gf.components.taper(taper_length, wire_width, width, layer=SI)
flare.dmovex(-taper_length)
slab = gf.components.rectangle((TRENCH_POSITIONS[-1] + 2 * margin, width), layer=SI)
trenches = gf.Component()
for x, w in zip(TRENCH_POSITIONS, TRENCH_WIDTHS):
trench = trenches << gf.components.rectangle((w, width), layer=SI)
trench.dmovex(margin + x - w / 2)
grating = c << gf.boolean(slab, trenches, "-", layer=SI)
grating.dmovey(-width / 2)
return c
@gf.cell
def focusing_grating(
period=0.63,
fill_factor=0.5,
wavelength=1.55,
fiber_angle=80, # between the fiber and the chip surface
first_order=20, # q of the first line
lines=24,
opening=37,
length=28,
wire_width=0.5,
wire_length=10,
):
"""A focusing grating: shallow-etched trenches along confocal ellipses whose
common focus is the end of the access wire, after Van Laere et al., IEEE
Photon. Technol. Lett. 2007."""
c = gf.Component()
phi = np.radians(opening) * np.linspace(-0.5, 0.5, 101)
arc = np.column_stack([np.cos(phi), np.sin(phi)])
c.add_polygon(np.vstack([(0, 0), length * arc]), layer=SI)
wire = c << gf.components.rectangle((wire_length, wire_width), layer=SI)
wire.dmove((-wire_length, -wire_width / 2))
c.add_port(
"o1", center=(-wire_length, 0), width=wire_width, orientation=180, layer=SI
)
# Line q: q * wavelength = n_eff * r - cos(fiber_angle) * x.
cos_angle = np.cos(np.radians(fiber_angle))
n_eff = wavelength / period + cos_angle
line = arc * (wavelength / (n_eff - cos_angle * np.cos(phi)))[:, None]
for q in range(first_order, first_order + lines):
trench = np.vstack([q * line, (q + 1 - fill_factor) * line[::-1]])
c.add_polygon(trench, layer=SHALLOW)
return c
@gf.cell
def grating_couplers(pitch=127):
"""The three couplers at the pitch of a fiber array."""
c = gf.Component()
c << uniform_grating()
(c << apodized_grating()).dmovey(-pitch)
(c << focusing_grating()).dmovey(-2 * pitch)
return c
if __name__ == "__main__":
import sys
grating_couplers().write_gds(sys.argv[1])
"""Uniform, apodized and focusing grating couplers, as
laylight/grating_couplers.lyl."""
import gdstk
import numpy as np
from tech import SHALLOW, SI
from waveguides import taper
# Trench centers (from the first one) and widths of the apodized grating in nm,
# Bozzola et al. 2015, Table 1.
# fmt: off
TRENCH_POSITIONS = np.array([
0, 712.7, 1377, 2061.7, 2769.7, 3429.3, 4159, 4846.6, 5569.2, 6250.5, 6982.2,
7655.1, 8351.4, 9057.9, 9734.4, 10452.8, 11121.2, 11813.3, 12512.3, 13197.8,
13916.8, 14583.6, 15317, 16017.2,
]) / 1000
TRENCH_WIDTHS = np.array([
100.3, 100.7, 130.1, 121.3, 127.8, 150.2, 154.8, 145.9, 164.8, 147.8, 153.4,
150.3, 133.3, 156.8, 162.5, 141, 164.5, 140.9, 146.3, 130.8, 142.2, 137.7,
114.3, 132.7,
]) / 1000
# fmt: on
def uniform_grating(
period=0.63,
fill_factor=0.5,
periods=20,
width=12,
taper_length=400,
wire_width=0.45,
):
"""A uniform grating of shallow-etched trenches at the end of a long linear
taper, after Taillaert et al., Jpn. J. Appl. Phys. 2006."""
cell = gdstk.Cell("UniformGrating")
y = width / 2
cell.add(taper(taper_length, wire_width, width, SI).translate(-taper_length, 0))
cell.add(gdstk.rectangle((0, -y), (periods * period, y), **SI))
w = (1 - fill_factor) * period
for i in range(periods):
x = (i + 0.5) * period
cell.add(gdstk.rectangle((x - w / 2, -y), (x + w / 2, y), **SHALLOW))
return cell
def apodized_grating(width=12, taper_length=400, wire_width=0.45, margin=1):
"""An apodized grating of fully etched trenches, after Bozzola et al., Opt.
Express 2015, with the taper of the uniform grating. `margin`: silicon
before the first and after the last trench center."""
cell = gdstk.Cell("ApodizedGrating")
y = width / 2
cell.add(taper(taper_length, wire_width, width, SI).translate(-taper_length, 0))
slab = gdstk.rectangle((0, -y), (TRENCH_POSITIONS[-1] + 2 * margin, y))
trenches = [
gdstk.rectangle((margin + x - w / 2, -y), (margin + x + w / 2, y))
for x, w in zip(TRENCH_POSITIONS, TRENCH_WIDTHS)
]
cell.add(*gdstk.boolean(slab, trenches, "not", **SI))
return cell
def focusing_grating(
period=0.63,
fill_factor=0.5,
wavelength=1.55,
fiber_angle=80, # between the fiber and the chip surface
first_order=20, # q of the first line
lines=24,
opening=37,
length=28,
wire_width=0.5,
wire_length=10,
):
"""A focusing grating: shallow-etched trenches along confocal ellipses whose
common focus is the end of the access wire, after Van Laere et al., IEEE
Photon. Technol. Lett. 2007."""
cell = gdstk.Cell("FocusingGrating")
a = np.radians(opening) / 2
cell.add(
gdstk.ellipse(
(0, 0), length, initial_angle=-a, final_angle=a, tolerance=1e-3, **SI
),
gdstk.rectangle((-wire_length, -wire_width / 2), (0, wire_width / 2), **SI),
)
# Line q: q * wavelength = n_eff * r - cos(fiber_angle) * x.
cos_angle = np.cos(np.radians(fiber_angle))
n_eff = wavelength / period + cos_angle
phi = np.linspace(-a, a, 101)
line = wavelength / (n_eff - cos_angle * np.cos(phi)) * np.exp(1j * phi)
for q in range(first_order, first_order + lines):
trench = np.concatenate([q * line, (q + 1 - fill_factor) * line[::-1]])
cell.add(gdstk.Polygon(trench, **SHALLOW))
return cell
def grating_couplers(pitch=127):
"""The three couplers at the pitch of a fiber array."""
cell = gdstk.Cell("GratingCouplers")
cell.add(gdstk.Reference(uniform_grating()))
cell.add(gdstk.Reference(apodized_grating(), (0, -pitch)))
cell.add(gdstk.Reference(focusing_grating(), (0, -2 * pitch)))
return cell
if __name__ == "__main__":
import sys
top = grating_couplers()
lib = gdstk.Library()
lib.add(top, *top.dependencies(True))
lib.write_gds(sys.argv[1])
"""Uniform, apodized and focusing grating couplers, as
laylight/grating_couplers.lyl."""
import klayout.db as kdb
import numpy as np
from tech import SHALLOW, SI, layout
from waveguides import polygon, taper
# Trench centers (from the first one) and widths of the apodized grating in nm,
# Bozzola et al. 2015, Table 1.
# fmt: off
TRENCH_POSITIONS = np.array([
0, 712.7, 1377, 2061.7, 2769.7, 3429.3, 4159, 4846.6, 5569.2, 6250.5, 6982.2,
7655.1, 8351.4, 9057.9, 9734.4, 10452.8, 11121.2, 11813.3, 12512.3, 13197.8,
13916.8, 14583.6, 15317, 16017.2,
]) / 1000
TRENCH_WIDTHS = np.array([
100.3, 100.7, 130.1, 121.3, 127.8, 150.2, 154.8, 145.9, 164.8, 147.8, 153.4,
150.3, 133.3, 156.8, 162.5, 141, 164.5, 140.9, 146.3, 130.8, 142.2, 137.7,
114.3, 132.7,
]) / 1000
# fmt: on
def uniform_grating(
period=0.63,
fill_factor=0.5,
periods=20,
width=12,
taper_length=400,
wire_width=0.45,
):
"""A uniform grating of shallow-etched trenches at the end of a long linear
taper, after Taillaert et al., Jpn. J. Appl. Phys. 2006."""
cell = layout.create_cell("UniformGrating")
si = cell.shapes(layout.layer(SI))
shallow = cell.shapes(layout.layer(SHALLOW))
y = width / 2
si.insert(taper(taper_length, wire_width, width).moved(-taper_length, 0))
si.insert(kdb.DBox(0, -y, periods * period, y))
w = (1 - fill_factor) * period
for i in range(periods):
x = (i + 0.5) * period
shallow.insert(kdb.DBox(x - w / 2, -y, x + w / 2, y))
return cell
def apodized_grating(width=12, taper_length=400, wire_width=0.45, margin=1):
"""An apodized grating of fully etched trenches, after Bozzola et al., Opt.
Express 2015, with the taper of the uniform grating. `margin`: silicon
before the first and after the last trench center."""
cell = layout.create_cell("ApodizedGrating")
si = cell.shapes(layout.layer(SI))
y = width / 2
si.insert(taper(taper_length, wire_width, width).moved(-taper_length, 0))
slab = kdb.DBox(0, -y, TRENCH_POSITIONS[-1] + 2 * margin, y)
trenches = kdb.Region()
for x, w in zip(TRENCH_POSITIONS, TRENCH_WIDTHS):
trench = kdb.DBox(margin + x - w / 2, -y, margin + x + w / 2, y)
trenches.insert(trench.to_itype(layout.dbu))
si.insert(kdb.Region(slab.to_itype(layout.dbu)) - trenches)
return cell
def focusing_grating(
period=0.63,
fill_factor=0.5,
wavelength=1.55,
fiber_angle=80, # between the fiber and the chip surface
first_order=20, # q of the first line
lines=24,
opening=37,
length=28,
wire_width=0.5,
wire_length=10,
):
"""A focusing grating: shallow-etched trenches along confocal ellipses whose
common focus is the end of the access wire, after Van Laere et al., IEEE
Photon. Technol. Lett. 2007."""
cell = layout.create_cell("FocusingGrating")
si = cell.shapes(layout.layer(SI))
shallow = cell.shapes(layout.layer(SHALLOW))
phi = np.radians(opening) * np.linspace(-0.5, 0.5, 101)
arc = np.column_stack([np.cos(phi), np.sin(phi)])
si.insert(polygon(np.vstack([(0, 0), length * arc])))
si.insert(kdb.DBox(-wire_length, -wire_width / 2, 0, wire_width / 2))
# Line q: q * wavelength = n_eff * r - cos(fiber_angle) * x.
cos_angle = np.cos(np.radians(fiber_angle))
n_eff = wavelength / period + cos_angle
line = arc * (wavelength / (n_eff - cos_angle * np.cos(phi)))[:, None]
for q in range(first_order, first_order + lines):
shallow.insert(
polygon(np.vstack([q * line, (q + 1 - fill_factor) * line[::-1]]))
)
return cell
def grating_couplers(pitch=127):
"""The three couplers at the pitch of a fiber array."""
cell = layout.create_cell("GratingCouplers")
couplers = [uniform_grating(), apodized_grating(), focusing_grating()]
for i, coupler in enumerate(couplers):
trans = kdb.DTrans(kdb.DVector(0, -i * pitch))
cell.insert(kdb.DCellInstArray(coupler.cell_index(), trans))
return cell
if __name__ == "__main__":
import sys
grating_couplers()
layout.write(sys.argv[1])