Thin-Film Lithium Niobate Modulator
A lithium niobate Mach-Zehnder modulator with micro-structured travelling-wave electrodes of T-shaped segments.
TFLNModulator from laylight/tfln_modulator.lyl, compiled in your browserOpen in playgroundThin-film lithium niobate (TFLN) modulators use the strong electro-optic effect of lithium niobate in a waveguide only about a micrometer wide, so that the electrodes can sit a few micrometers from the light. A Mach-Zehnder interferometer has one arm in each gap of a ground-signal-ground travelling-wave electrode. Bringing the electrodes closer lowers the drive voltage but raises the microwave loss, which limits the bandwidth. Kharel et al. break this trade-off with micro-structured electrodes: rows of T-shaped segments along the rail edges carry the field to the waveguide across a 5 µm gap, while the rails themselves stay far apart and keep the microwave loss of a wide electrode.
The example draws the modulator of the paper's Fig. 1: a 100 µm signal rail between two ground rails, 10 mm long, and on each of the four edges that face a waveguide a row of T-shaped segments with a period of 50 µm (6 µm stems, 45 µm × 2 µm heads, 5 µm between heads), whose heads face each other across the 5 µm gap. The 1.2 µm lithium niobate ridges run through the middle of the two gaps; at both ends S-bends from a common point form Y-branches. Each rail tapers into a probe pad at both ends.
References
- P. Kharel, C. Reimer, K. Luke, L. He and M. Zhang, "Breaking voltage–bandwidth limits in integrated lithium niobate modulators using micro-structured electrodes," Optica 8(3), 357 (2021). doi:10.1364/OPTICA.416155
Parameters
| Parameter | Value | Source |
|---|---|---|
| Electrode length | 10 mm | Kharel 2021, Sec. III (10 and 20 mm devices) |
| Signal width, gap | 100 µm, 5 µm between T-heads | Kharel 2021, Table I |
| T-segments | stem 6 × 6 µm, head 45 × 2 µm, 5 µm between heads (50 µm period) | Kharel 2021, Fig. 1 |
| Splitters | Y-branches | Kharel 2021, Fig. 1(a) |
| Ground width | 150 µm | assumed |
| Ridge width | 1.2 µm | assumed |
| S-bends, pad tapers, pads | 500 µm, 100 µm, 80 µm | assumed |
How it is written
The T-segments are the heart of the device: one loop runs over the four rail edges, each given as a frame whose direction points from the rail into the gap, and an inner loop places a stem on the edge and a head on the stem's far side. Because the frame carries the direction, the same two lines draw segments that point up on one edge and down on the next. A second loop over the three rails tapers each one into a pad at both ends, reading the rail's height for the taper. Laylight's version is the shortest of the four, in lines and in tokens.
Implementations
| Implementation | Lines of code | Tokens | Layout |
|---|---|---|---|
| Laylight | 48 | 401 | reference |
| gdsfactory | 61 (1.27×) | 582 (1.45×) | same within 2 nm |
| gdstk | 60 (1.25×) | 566 (1.41×) | same within 2 nm |
| KLayout | 61 (1.27×) | 666 (1.66×) | same within 2 nm |
import "tech.lyl"
# A thin-film lithium niobate Mach-Zehnder modulator with micro-structured
# (T-shaped) travelling-wave electrodes, after Kharel et al., Optica 2021.
cell TFLNModulator(
length = 10mm, # electrode length
signal_width = 100um,
ground_width = 150um,
gap = 5um, # g: between the T-heads on either side of an arm
stem_length = 6um, # h
stem_width = 6um, # t
head_width = 2um, # s
head_length = 45um, # r
head_gap = 5um, # c: between neighbouring T-heads
width = 1.2um, # LN ridge
sbend_length = 500um,
taper_length = 100um, # from the electrodes to the probe pads
pad_size = 80um,
) {
attribute {
channel gap + 2 * (stem_length + head_width) # between the rails
}
# Ground-signal-ground rails with rows of T-shaped segments along the four
# edges that face an arm, tapered to probe pads at both ends.
layer metal {
shape rect with length, signal_width as signal.left_center at self.origin
shape rect with length, ground_width as upper
place upper.bottom_left at signal.top_left offset (0, channel)
clone upper as lower.top_left at signal.bottom_left offset (0, -channel)
define y = signal_width / 2
define pitch = head_length + head_gap
for edge in [
(0, y, 90deg),
(0, -y, -90deg),
(0, y + channel, -90deg),
(0, -y - channel, 90deg)
] {
for i in 0..round(length / pitch) {
shape rect with stem_length, stem_width as stem
place stem.left_center at edge + ((i + 0.5) * pitch, 0)
shape rect with head_width, head_length as head
place head.left_center at stem.right_center
}
}
for line in [signal, upper, lower] {
shape taper with taper_length, line.height, pad_size as in_taper
place in_taper.start at line.left_center rotate 180deg
clone in_taper as out_taper.start at line.right_center
shape rect with pad_size, pad_size as in_pad.left_center at in_taper.end
clone in_pad as out_pad.left_center at out_taper.end
}
}
# The arms run in the middle of the gaps; S-bends from a common point
# form the Y-branches that split and combine the light.
layer ln {
for side in [1, -1] {
define y = side * (signal_width + channel) / 2
shape rect with length + 2 * (taper_length + pad_size), width as arm
place arm.center at (length / 2, y)
shape sbend with sbend_length, y, width as split.end at arm.left_center
shape sbend with sbend_length, -y, width as combine
place combine.start at arm.right_center
}
}
}
"""A thin-film lithium niobate Mach-Zehnder modulator with T-shaped travelling-wave
electrodes, as laylight/tfln_modulator.lyl."""
import gdsfactory as gf
from tech import LN, METAL
@gf.cell
def tee(stem_length=6, stem_width=6, head_width=2, head_length=45):
"""A T-shaped electrode segment pointing up from (0, 0)."""
c = gf.Component()
stem = c << gf.components.rectangle((stem_width, stem_length), layer=METAL)
head = c << gf.components.rectangle((head_length, head_width), layer=METAL)
stem.dmove((-stem_width / 2, 0))
head.dmove((-head_length / 2, stem_length))
return c
@gf.cell
def tfln_modulator(
length=10000,
signal_width=100,
ground_width=150,
gap=5,
stem_length=6,
stem_width=6,
head_width=2,
head_length=45,
head_gap=5,
width=1.2,
sbend_length=500,
taper_length=100,
pad_size=80,
):
"""GSG electrodes with rows of T-shaped segments along the edges that face the
arms, tapered to probe pads; the arms are split and combined by Y-branches."""
c = gf.Component()
channel = gap + 2 * (stem_length + head_width) # between the rails
pitch = head_length + head_gap
segment = tee(stem_length, stem_width, head_width, head_length)
y = signal_width / 2
for edge, mirror in [
(y, False),
(-y, True),
(y + channel, True),
(-y - channel, False),
]:
row = c.add_ref(segment, columns=round(length / pitch), column_pitch=pitch)
if mirror:
row.dmirror_y()
row.dmove((pitch / 2, edge))
# Each line with its tapers and pads as one outline.
end = taper_length + pad_size
x = [-end, -taper_length, 0, length, length + taper_length, length + end]
y_ground = y + channel + ground_width / 2
for yc, w in [
(0, signal_width),
(y_ground, ground_width),
(-y_ground, ground_width),
]:
half = [pad_size / 2, pad_size / 2, w / 2, w / 2, pad_size / 2, pad_size / 2]
upper = [(xi, yc + h) for xi, h in zip(x, half)]
lower = [(xi, yc - h) for xi, h in zip(x, half)]
c.add_polygon(upper + lower[::-1], layer=METAL)
xs = gf.cross_section.cross_section(width=width, layer=LN)
for ya in [y + channel / 2, -y - channel / 2]:
arm = c << gf.components.straight(length + 2 * end, cross_section=xs)
arm.dmove((-end, ya))
split = c << gf.components.bend_s(
(sbend_length, ya), npoints=200, cross_section=xs
)
split.connect("o2", arm.ports["o1"])
combine = c << gf.components.bend_s(
(sbend_length, -ya), npoints=200, cross_section=xs
)
combine.connect("o1", arm.ports["o2"])
return c
if __name__ == "__main__":
import sys
tfln_modulator().write_gds(sys.argv[1])
"""A thin-film lithium niobate Mach-Zehnder modulator with T-shaped travelling-wave
electrodes, as laylight/tfln_modulator.lyl."""
import gdstk
from tech import LN, METAL
def tee(stem_length=6, stem_width=6, head_width=2, head_length=45):
"""A T-shaped electrode segment pointing up from (0, 0)."""
cell = gdstk.Cell("Tee")
y = stem_length + head_width
stem = gdstk.rectangle((-stem_width / 2, 0), (stem_width / 2, stem_length), **METAL)
head = gdstk.rectangle(
(-head_length / 2, stem_length), (head_length / 2, y), **METAL
)
cell.add(stem, head)
return cell
def tfln_modulator(
length=10000,
signal_width=100,
ground_width=150,
gap=5,
stem_length=6,
stem_width=6,
head_width=2,
head_length=45,
head_gap=5,
width=1.2,
sbend_length=500,
taper_length=100,
pad_size=80,
):
"""GSG electrodes with rows of T-shaped segments along the edges that face the
arms, tapered to probe pads; the arms are split and combined by Y-branches."""
cell = gdstk.Cell("TFLNModulator")
channel = gap + 2 * (stem_length + head_width) # between the rails
pitch = head_length + head_gap
segment = tee(stem_length, stem_width, head_width, head_length)
y = signal_width / 2
for edge, mirror in [
(y, False),
(-y, True),
(y + channel, True),
(-y - channel, False),
]:
cell.add(
gdstk.Reference(
segment,
(pitch / 2, edge),
x_reflection=mirror,
columns=round(length / pitch),
spacing=(pitch, 0),
)
)
# Each line with its tapers and pads as one outline.
end = taper_length + pad_size
x = [-end, -taper_length, 0, length, length + taper_length, length + end]
y_ground = y + channel + ground_width / 2
for yc, w in [
(0, signal_width),
(y_ground, ground_width),
(-y_ground, ground_width),
]:
half = [pad_size / 2, pad_size / 2, w / 2, w / 2, pad_size / 2, pad_size / 2]
upper = [(xi, yc + h) for xi, h in zip(x, half)]
lower = [(xi, yc - h) for xi, h in zip(x, half)]
cell.add(gdstk.Polygon(upper + lower[::-1], **METAL))
mid = sbend_length / 2
for ya in [y + channel / 2, -y - channel / 2]:
arm = gdstk.FlexPath((-end - sbend_length, 0), width, tolerance=1e-3, **LN)
arm.bezier([(mid, 0), (mid, ya), (sbend_length, ya)], relative=True)
arm.segment((length + 2 * end, 0), relative=True)
arm.bezier([(mid, 0), (mid, -ya), (sbend_length, -ya)], relative=True)
cell.add(arm)
return cell
if __name__ == "__main__":
import sys
top = tfln_modulator()
lib = gdstk.Library()
lib.add(top, *top.dependencies(True))
lib.write_gds(sys.argv[1])
"""A thin-film lithium niobate Mach-Zehnder modulator with T-shaped travelling-wave
electrodes, as laylight/tfln_modulator.lyl."""
import klayout.db as kdb
import numpy as np
from tech import LN, METAL, layout
from waveguides import path, polygon
def tee(stem_length=6, stem_width=6, head_width=2, head_length=45):
"""A T-shaped electrode segment pointing up from (0, 0)."""
cell = layout.create_cell("Tee")
metal = cell.shapes(layout.layer(METAL))
y = stem_length + head_width
metal.insert(kdb.DBox(-stem_width / 2, 0, stem_width / 2, stem_length))
metal.insert(kdb.DBox(-head_length / 2, stem_length, head_length / 2, y))
return cell
def tfln_modulator(
length=10000,
signal_width=100,
ground_width=150,
gap=5,
stem_length=6,
stem_width=6,
head_width=2,
head_length=45,
head_gap=5,
width=1.2,
sbend_length=500,
taper_length=100,
pad_size=80,
):
"""GSG electrodes with rows of T-shaped segments along the edges that face the
arms, tapered to probe pads; the arms are split and combined by Y-branches."""
cell = layout.create_cell("TFLNModulator")
metal = cell.shapes(layout.layer(METAL))
ln = cell.shapes(layout.layer(LN))
channel = gap + 2 * (stem_length + head_width) # between the rails
pitch = head_length + head_gap
segment = tee(stem_length, stem_width, head_width, head_length).cell_index()
columns = round(length / pitch)
y = signal_width / 2
for edge, mirror in [
(y, False),
(-y, True),
(y + channel, True),
(-y - channel, False),
]:
trans = kdb.DCplxTrans(1, 0, mirror, pitch / 2, edge)
step = kdb.DVector(pitch, 0)
cell.insert(kdb.DCellInstArray(segment, trans, step, kdb.DVector(), columns, 1))
# Each line with its tapers and pads as one outline.
end = taper_length + pad_size
x = [-end, -taper_length, 0, length, length + taper_length, length + end]
y_ground = y + channel + ground_width / 2
for yc, w in [
(0, signal_width),
(y_ground, ground_width),
(-y_ground, ground_width),
]:
half = [pad_size / 2, pad_size / 2, w / 2, w / 2, pad_size / 2, pad_size / 2]
upper = [(xi, yc + h) for xi, h in zip(x, half)]
lower = [(xi, yc - h) for xi, h in zip(x, half)]
metal.insert(polygon(upper + lower[::-1]))
# S-bends: cubic Bezier curves, as gdsfactory's bend_s.
u = np.linspace(0, 1, 500)[:, None]
weights = np.hstack([(1 - u) ** 3, 3 * u * (1 - u) ** 2, 3 * u**2 * (1 - u), u**3])
mid = sbend_length / 2
for ya in [y + channel / 2, -y - channel / 2]:
control = np.array([(0, 0), (mid, 0), (mid, ya), (sbend_length, ya)])
split = weights @ control - (end + sbend_length, 0)
combine = split[::-1] * (-1, 1) + (length, 0) # mirrored about the middle
ln.insert(path(np.vstack([split, combine]), width))
return cell
if __name__ == "__main__":
import sys
tfln_modulator()
layout.write(sys.argv[1])