Microring Modulator
A carrier-injection silicon microring modulator: a p-i-n diode across a 5 µm rib ring, wired to ground-signal-ground pads.
RingModulator from laylight/ring_modulator.lyl, compiled in your browserOpen in playgroundSilicon microring modulators change the refractive index of the ring by moving free carriers in and out of it: injecting carriers lowers the index and shifts the ring's resonance, so light at a wavelength on the resonance edge is switched on and off. In a carrier-injection modulator the ring waveguide is the intrinsic region of a p-i-n diode.
The example follows the injection rings of Xu et al.: a rib ring of 5 µm radius, 450 nm wide on a thin slab, 200 nm from a straight bus. The p++ region is a disk inside the ring and the n++ region surrounds it, both 300 nm from the waveguide edges; the n++ region stays open along the bus, so the diode nearly encloses the ring. A contact sits on the p++ disk and two on the n++ region; metal lines lead from them to a row of ground-signal-ground probe pads.
References
- Q. Xu et al., "12.5 Gbit/s carrier-injection-based silicon micro-ring silicon modulators," Optics Express 15(2), 430–436 (2007). doi:10.1364/OE.15.000430
- Q. Xu et al., "Micrometre-scale silicon electro-optic modulator," Nature 435, 325–327 (2005). doi:10.1038/nature03569
Parameters
| Parameter | Value | Source |
|---|---|---|
| Ring radius | 5 µm | Xu 2007, Sec. 2 |
| Waveguides | 450 nm rib on a thin slab | Xu 2007, Sec. 2 |
| Gap to the bus | 200 nm | Xu 2007, Sec. 2 |
| Doped regions to the waveguide edges | 300 nm | Xu 2007, Sec. 2, Fig. 1 |
| Slab | 20 µm square | assumed |
| Contacts, metal lines | 2 µm contacts, 3 µm lines | assumed |
| Pads | 80 µm, 100 µm pitch | assumed |
How it is written
The doped regions are geometry derived from the waveguides. The p++ disk is a circle whose radius is computed from the ring's. The n++ region is one boolean expression on its own layer: the slab, minus a disk around the ring, minus the bus grown by the 300 nm doping gap, slab - keepout - (bus + doping_gap). The operands stay on their own layers, so the bus is still drawn in silicon. gdstk and gdsfactory write the same subtraction with gdstk.boolean and KLayout regions.
Implementations
| Implementation | Lines of code | Tokens | Layout |
|---|---|---|---|
| Laylight | 23 | 282 | reference |
| gdsfactory | 24 (1.04×) | 383 (1.36×) | same within 3 nm |
| gdstk | 26 (1.13×) | 444 (1.57×) | same within 3 nm |
| KLayout | 35 (1.52×) | 545 (1.93×) | same within 2 nm |
import "tech.lyl"
# A carrier-injection microring modulator after Xu et al., Opt. Express 2007: a
# p-i-n diode across a rib ring on a 50 nm slab, p++ inside the ring and n++
# outside it and beyond the bus, `doping_gap` from the waveguides. The square
# slab, `slab_size` wide and crossed by the bus, and the electrodes are assumed:
# contacts on the p++ disk and on either side of the n++ region, with metal
# lines to ground-signal-ground pads.
cell RingModulator(
radius = 5um, width = 450nm, gap = 200nm, doping_gap = 300nm, slab_size = 20um
) {
layer si {
shape ring with radius, width as ring at self.origin
shape rect with slab_size, width as bus at (0, -radius - width - gap)
}
layer slab { shape rect with slab_size, slab_size as slab at self.origin }
layer ppp {
shape circle with radius - width / 2 - doping_gap as p_region at self.origin
}
layer npp {
shape circle with radius + width / 2 + doping_gap as keepout
shape slab - keepout - (bus + doping_gap) as n_region
}
layer contact {
for x in [-radius - 3um, 0, radius + 3um] {
shape rect with 2um, 2um as c at (x, 0)
}
}
# Lines with square ends that cover the contacts, and 80 um pads.
layer metal {
shape path with [(0, 0), (0, 60um)], 3um, ends = "square" as signal
place signal at self.origin
for x in [-radius - 3um, radius + 3um] {
define px = sign(x) * 100um
shape path with [(x, 0), (px, 0), (px, 60um)], 3um, ends = "square" as line
place line at self.origin
}
for x in [-100um, 0, 100um] { shape rect with 80um, 80um as pad at (x, 60um) }
}
}
"""A carrier-injection microring modulator, as laylight/ring_modulator.lyl."""
import gdsfactory as gf
from tech import CONTACT, METAL, NPP, PPP, SI, SLAB
@gf.cell
def ring_modulator(radius=5, width=0.45, gap=0.2, doping_gap=0.3, slab_size=20):
"""A carrier-injection microring modulator: a p-i-n diode across a 5 um rib
ring, p++ inside the ring and n++ outside it and beyond the bus, after Xu et
al., Opt. Express 2007. The slab size and the electrodes are assumed:
contacts on the p++ disk and on either side of the n++ region, with metal
lines to ground-signal-ground pads."""
c = gf.Component()
c << gf.components.ring(radius, width, layer=SI)
bus = c << gf.components.rectangle((slab_size, width), SI, centered=True)
bus.dmovey(-radius - width - gap)
c << gf.components.rectangle((slab_size, slab_size), SLAB, centered=True)
c << gf.components.circle(radius - width / 2 - doping_gap, layer=PPP)
# n++ fills the slab but for doping_gap around the waveguides and the ring.
keepout = gf.components.circle(radius + width / 2 + doping_gap, layer=NPP)
margin = c.get_region(SI).sized(gf.kcl.to_dbu(doping_gap))
c.add_polygon(c.get_region(SLAB) - keepout.get_region(NPP) - margin, layer=NPP)
for x in [-radius - 3, 0, radius + 3]:
(c << gf.components.rectangle((2, 2), CONTACT, centered=True)).dmovex(x)
# Lines that start 1.5 um before the contacts to cover them, and 80 um pads.
lines = [[(0, -1.5), (0, 60)]]
for side in [-1, 1]:
x, px = side * (radius + 1.5), side * 100
lines.append([(x, 0), (px, 0), (px, 60)])
for line in lines:
c << gf.Path(line).extrude(width=3, layer=METAL)
for x in [-100, 0, 100]:
(c << gf.components.pad((80, 80), layer=METAL)).dmove((x, 60))
return c
if __name__ == "__main__":
import sys
ring_modulator().write_gds(sys.argv[1])
"""A carrier-injection microring modulator, as laylight/ring_modulator.lyl."""
import gdstk
from tech import CONTACT, METAL, NPP, PPP, SI, SLAB
def ring_modulator(radius=5, width=0.45, gap=0.2, doping_gap=0.3, slab_size=20):
"""A carrier-injection microring modulator: a p-i-n diode across a 5 um rib
ring, p++ inside the ring and n++ outside it and beyond the bus, after Xu et
al., Opt. Express 2007. The slab size and the electrodes are assumed:
contacts on the p++ disk and on either side of the n++ region, with metal
lines to ground-signal-ground pads."""
cell = gdstk.Cell("RingModulator")
r1, r2 = radius - width / 2, radius + width / 2
s, y = slab_size / 2, -radius - width - gap # y: the bus center
cell.add(gdstk.ellipse((0, 0), r2, inner_radius=r1, tolerance=1e-3, **SI))
cell.add(gdstk.rectangle((-s, y - width / 2), (s, y + width / 2), **SI))
slab = gdstk.rectangle((-s, -s), (s, s), **SLAB)
cell.add(slab)
cell.add(gdstk.ellipse((0, 0), r1 - doping_gap, tolerance=1e-3, **PPP))
keepout = gdstk.ellipse((0, 0), r2 + doping_gap, tolerance=1e-3)
d = width / 2 + doping_gap
band = gdstk.rectangle((-s, y - d), (s, y + d))
cell.add(*gdstk.boolean(slab, [keepout, band], "not", **NPP))
for x in [-radius - 3, 0, radius + 3]:
cell.add(gdstk.rectangle((x - 1, -1), (x + 1, 1), **CONTACT))
# Lines with square ends that cover the contacts, and 80 um pads.
lines = [[(0, 0), (0, 60)]]
for side in [-1, 1]:
x, px = side * (radius + 3), side * 100
lines.append([(x, 0), (px, 0), (px, 60)])
for line in lines:
cell.add(gdstk.FlexPath(line, 3, ends="extended", **METAL))
for x in [-100, 0, 100]:
cell.add(gdstk.rectangle((x - 40, 20), (x + 40, 100), **METAL))
return cell
if __name__ == "__main__":
import sys
top = ring_modulator()
lib = gdstk.Library()
lib.add(top, *top.dependencies(True))
lib.write_gds(sys.argv[1])
"""A carrier-injection microring modulator, as laylight/ring_modulator.lyl."""
import klayout.db as kdb
import numpy as np
from tech import CONTACT, METAL, NPP, PPP, SI, SLAB, layout
from waveguides import polygon
def disk(radius, n=500):
"""A disk of `radius` around the origin, as a region (database units)."""
a = np.linspace(0, 2 * np.pi, n, endpoint=False)
points = radius * np.column_stack([np.cos(a), np.sin(a)])
return kdb.Region(polygon(points).to_itype(layout.dbu))
def box(x1, y1, x2, y2):
"""A rectangle, as a region (database units)."""
return kdb.Region(kdb.DBox(x1, y1, x2, y2).to_itype(layout.dbu))
def ring_modulator(radius=5, width=0.45, gap=0.2, doping_gap=0.3, slab_size=20):
"""A carrier-injection microring modulator: a p-i-n diode across a 5 um rib
ring, p++ inside the ring and n++ outside it and beyond the bus, after Xu et
al., Opt. Express 2007. The slab size and the electrodes are assumed:
contacts on the p++ disk and on either side of the n++ region, with metal
lines to ground-signal-ground pads."""
cell = layout.create_cell("RingModulator")
r1, r2 = radius - width / 2, radius + width / 2
s, y = slab_size / 2, -radius - width - gap # y: the bus center
si = disk(r2) - disk(r1) + box(-s, y - width / 2, s, y + width / 2)
cell.shapes(layout.layer(SI)).insert(si)
slab = box(-s, -s, s, s)
cell.shapes(layout.layer(SLAB)).insert(slab)
cell.shapes(layout.layer(PPP)).insert(disk(r1 - doping_gap))
d = width / 2 + doping_gap
n = slab - disk(r2 + doping_gap) - box(-s, y - d, s, y + d)
cell.shapes(layout.layer(NPP)).insert(n)
contact = cell.shapes(layout.layer(CONTACT))
for x in [-radius - 3, 0, radius + 3]:
contact.insert(kdb.DBox(x - 1, -1, x + 1, 1))
# Lines with square ends that cover the contacts, and 80 um pads.
metal = cell.shapes(layout.layer(METAL))
lines = [[(0, 0), (0, 60)]]
for side in [-1, 1]:
x, px = side * (radius + 3), side * 100
lines.append([(x, 0), (px, 0), (px, 60)])
for line in lines:
metal.insert(kdb.DPath([kdb.DPoint(*p) for p in line], 3, 1.5, 1.5))
for x in [-100, 0, 100]:
metal.insert(kdb.DBox(x - 40, 20, x + 40, 100))
return cell
if __name__ == "__main__":
import sys
ring_modulator()
layout.write(sys.argv[1])