Spiral Delay Line
Two interleaved Archimedean spirals of a 2 µm multimode waveguide, joined by an Euler S-bend.
DelayLine from laylight/delay_line.lyl, compiled in your browserOpen in playgroundAn optical delay line is a long waveguide folded into a small area. A double spiral is the compact way to do it: the light runs inward along one spiral, turns around in an S-bend at the center and runs outward along a second spiral interleaved with the first, so input and output end up on opposite sides. Hong et al. made such spirals with ultralow loss by using a 2 µm wide multimode waveguide, in which the fundamental mode barely touches the sidewalls, and Euler bends in the S-bend, which do not excite the higher-order modes.
In the example, the light enters through a 450 nm waveguide and a bent taper, a quarter circle whose width grows to 2 µm, runs inward along an Archimedean spiral, turns in an S-bend of two Euler bends, and leaves along the interleaved outward spiral and a second bent taper. The waveguides are 1.5 µm apart, so each spiral advances by 7 µm per turn. The outward half is the inward half turned by 180°. With the default of 3 turns per spiral the waveguide is 1.5 mm long; with 30 turns it is 4.9 cm, with an outer radius of 235 µm, close to the paper's 5 cm spiral.
Two simplifications: the paper's S-bend uses a modified Euler curve whose curvature ranges from 1/25 µm⁻¹ to 1/10 µm⁻¹, and narrows to 0.6 µm at the junction. The example uses standard Euler bends with the same minimum radius of 10 µm and keeps the full width, so its S-bend is 42.6 µm tall instead of 32 µm.
References
- S. Hong et al., "Ultralow-loss compact silicon photonic waveguide spirals and delay lines," Photonics Research 10(1), 1–7 (2022). doi:10.1364/PRJ.437726
Parameters
| Parameter | Value | Source |
|---|---|---|
| Waveguide width | 2 µm | Hong 2022 |
| Gap between waveguides | 1.5 µm (7 µm pitch per spiral) | Hong 2022 (pitch derived) |
| Turns per spiral | 3 | assumed (30 for the paper's 5 cm) |
| Radius where the spirals meet the S-bend | 25 µm | Hong 2022 (its R_max) |
| S-bend | two Euler bends, minimum radius 10 µm | Hong 2022 (standard Euler bends, constant width) |
| Input and output | 450 nm, bent tapers of 25 µm radius | assumed |
How it is written
The spiral is a path through points computed in a loop from the spiral's equation; the outward spiral is a clone placed with rotate 180deg. The two Euler bends of the S-bend are placed by their end on a frame at the center whose direction is computed from where the spiral ends. The bent tapers are where Laylight is at a disadvantage: it has no bent taper or path of varying width, so the taper is a polygon whose two edges are computed in two loops. gdstk draws it with a single call, an arc whose width changes along it, and chains the taper, the spiral and the S-bend into one path, which makes its version the shortest here.
Where a sampled path meets an exact primitive such as euler, the path's end face is perpendicular to its last rounded segment rather than to the curve, which leaves notches of a few nanometers; the spiral uses ends = "round" to close them.
Implementations
| Implementation | Lines of code | Tokens | Layout |
|---|---|---|---|
| Laylight | 40 | 424 | reference |
| gdsfactory | 32 (0.80×) | 351 (0.83×) | same within 2 nm |
| gdstk | 27 (0.68×) | 314 (0.74×) | same within 2 nm |
| KLayout | 36 (0.90×) | 462 (1.09×) | same within 2 nm |
import "tech.lyl"
# A spiral delay line after Hong et al., Photonics Res. 2022: two interleaved
# Archimedean spirals of a multimode waveguide, joined at the centre by an S-bend
# of two Euler bends, with bent tapers to the single-mode input and output.
cell DelayLine(
width = 2um,
gap = 1.5um, # between neighbouring waveguides
turns number = 3, # of each spiral
inner_radius = 25um, # where the spirals meet the S-bend
p = 0.74, # Euler fraction of the S-bend: min radius 10 um
io_width = 450nm,
taper_radius = 25um,
) {
layer si {
# The inward spiral starts at the polar angle where it heads straight up
# (an Archimedean spiral crosses its circles at the angle atan(b / r));
# the outward one is it turned by 180deg.
define b = (width + gap) / pi # growth of the radius per radian
define outer = inner_radius + 2 * (width + gap) * turns
define start = 180deg + atan(b / outer)
define n = round(720 * turns) # 0.5deg steps
define spiral: [point] = []
for i in 0..n + 1 {
define r = outer - (outer - inner_radius) * i / n
define a = start - turns * 360deg * i / n
spiral.append((r * cos(a), r * sin(a)))
}
shape path with spiral, width, ends = "round" as inward # closes the joints
place inward at self.origin
clone inward as outward at self.origin rotate 180deg
# Each half of the S-bend turns by 180deg - 2 skew to meet the spiral.
define skew = atan(b / inner_radius)
define radius = inner_radius / (2 * cos(skew)) # the chord is inner_radius
shape euler with radius, width, 2 * skew - 180deg, p = p as first
clone first as second
define heading = start - turns * 360deg + 90deg + skew # at the centre
place first.end at (0, 0, heading)
place second.end at (0, 0, heading + 180deg)
# The bent tapers: quarter circles whose width grows linearly to width.
define taper: [point] = []
for i in 0..181 {
define r = taper_radius + (io_width + (width - io_width) * i / 180) / 2
taper.append((r * sin(i * 0.5deg), -r * cos(i * 0.5deg)))
}
for i in 0..181 {
define r = taper_radius - (width - (width - io_width) * i / 180) / 2
taper.append((r * cos(i * 0.5deg), -r * sin(i * 0.5deg)))
}
shape polygon with taper as input
place input at (inward.start.x - taper_radius, inward.start.y)
clone input as output
place output at (taper_radius - inward.start.x, -inward.start.y) rotate 180deg
}
}
"""A spiral delay line, as laylight/delay_line.lyl."""
import gdsfactory as gf
import numpy as np
from tech import SI
@gf.cell
def delay_line(
width=2,
gap=1.5,
turns=3,
inner_radius=25,
p=0.74,
io_width=0.45,
taper_radius=25,
):
"""Two interleaved Archimedean spirals of a multimode waveguide, joined at the
centre by an S-bend of two Euler bends, with bent tapers to the single-mode
input and output."""
c = gf.Component()
# The inward spiral starts at the polar angle where it heads straight up (an
# Archimedean spiral crosses its circles at the angle atan(b / r)); the
# outward one is it turned by 180 degrees.
b = (width + gap) / np.pi # growth of the radius per radian
outer = inner_radius + 2 * (width + gap) * turns
t = np.linspace(0, 1, round(720 * turns) + 1) # 0.5 degree steps
a = np.pi + np.arctan(b / outer) - 2 * np.pi * turns * t
r = outer - (outer - inner_radius) * t
skew = np.arctan(b / inner_radius)
points = np.column_stack([r * np.cos(a), r * np.sin(a)])
end_angle = np.degrees(a[-1] - skew) - 90
spiral = gf.Path(points, start_angle=90, end_angle=end_angle) # exact tangents
# Each half of the S-bend turns by 180 degrees - 2 skew to meet the spiral.
radius = inner_radius / (2 * np.cos(skew)) # the chord is inner_radius
bend = gf.path.euler(radius, 2 * np.degrees(skew) - 180, p, use_eff=True)
half = (spiral + bend).extrude(width=width, layer=SI)
# The bent taper: a quarter circle whose width grows linearly to width.
xs = gf.cross_section.strip
taper = gf.path.arc(taper_radius, npoints=181).extrude_transition(
gf.path.transition(xs(io_width, SI), xs(width, SI), width_type="linear")
)
for angle in [0, 180]:
(c << half).drotate(angle)
(c << taper).dmove(points[0] - taper_radius).drotate(angle)
return c
if __name__ == "__main__":
import sys
delay_line().write_gds(sys.argv[1])
"""A spiral delay line, as laylight/delay_line.lyl."""
import gdstk
import numpy as np
from tech import SI
from waveguides import euler
def delay_line(
width=2,
gap=1.5,
turns=3,
inner_radius=25,
p=0.74,
io_width=0.45,
taper_radius=25,
):
"""Two interleaved Archimedean spirals of a multimode waveguide, joined at the
centre by an S-bend of two Euler bends, with bent tapers to the single-mode
input and output."""
cell = gdstk.Cell("DelayLine")
# The inward spiral starts at the polar angle where it heads straight up (an
# Archimedean spiral crosses its circles at the angle atan(b / r)); the
# outward one is it turned by 180 degrees.
b = (width + gap) / np.pi # growth of the radius per radian
outer = inner_radius + 2 * (width + gap) * turns
t = np.linspace(0, 1, round(720 * turns) + 1) # 0.5 degree steps
a = np.pi + np.arctan(b / outer) - 2 * np.pi * turns * t
spiral = (outer - (outer - inner_radius) * t) * np.exp(1j * a)
# Each half of the S-bend turns by 180 degrees - 2 skew to meet the spiral.
skew = np.arctan(b / inner_radius)
bend = euler(1, 2 * np.degrees(skew) - 180, p) @ [1, 1j] # as x + iy
bend *= inner_radius / abs(bend[-1]) * np.exp(1j * (a[-1] - np.pi / 2 - skew))
# The bent taper from the input, the spiral and the S-bend, as one path.
start = spiral[0] - taper_radius * (1 + 1j)
half = gdstk.FlexPath(start, io_width, tolerance=1e-3, **SI)
half.arc(taper_radius, -np.pi / 2, 0, width=width)
half.segment(np.concatenate([spiral[1:], spiral[-1] + bend[1:]]))
cell.add(half, half.copy().rotate(np.pi))
return cell
if __name__ == "__main__":
import sys
top = delay_line()
lib = gdstk.Library()
lib.add(top, *top.dependencies(True))
lib.write_gds(sys.argv[1])
"""A spiral delay line, as laylight/delay_line.lyl."""
import klayout.db as kdb
import numpy as np
from tech import SI, layout
from waveguides import euler
def delay_line(
width=2,
gap=1.5,
turns=3,
inner_radius=25,
p=0.74,
io_width=0.45,
taper_radius=25,
):
"""Two interleaved Archimedean spirals of a multimode waveguide, joined at the
centre by an S-bend of two Euler bends, with bent tapers to the single-mode
input and output."""
cell = layout.create_cell("DelayLine")
si = cell.shapes(layout.layer(SI))
# The inward spiral starts at the polar angle where it heads straight up (an
# Archimedean spiral crosses its circles at the angle atan(b / r)); the
# outward one is it turned by 180 degrees.
b = (width + gap) / np.pi # growth of the radius per radian
outer = inner_radius + 2 * (width + gap) * turns
t = np.linspace(0, 1, round(720 * turns) + 1) # 0.5 degree steps
a = np.pi + np.arctan(b / outer) - 2 * np.pi * turns * t
spiral = (outer - (outer - inner_radius) * t) * np.exp(1j * a)
# Each half of the S-bend turns by 180 degrees - 2 skew to meet the spiral.
skew = np.arctan(b / inner_radius)
bend = euler(1, 2 * np.degrees(skew) - 180, p) @ [1, 1j] # as x + iy
bend *= inner_radius / abs(bend[-1]) * np.exp(1j * (a[-1] - np.pi / 2 - skew))
inward = np.concatenate([spiral, spiral[-1] + bend[1:]])
# Round ends close the joint to the taper. A polygon, since GDS paths hold at
# most 8191 points (KLayout splits long polygons when writing).
points = [kdb.DPoint(z.real, z.imag) for z in inward]
half = kdb.DPath(points, width, width / 2, width / 2, True).polygon()
# The bent taper: a quarter circle whose width grows linearly to width.
phi = np.linspace(0, np.pi / 2, 181) # 0.5 degree steps
w = io_width + (width - io_width) * phi / (np.pi / 2)
arc = np.exp(1j * (phi - np.pi / 2))
edge = np.concatenate(
[(taper_radius + w / 2) * arc, ((taper_radius - w / 2) * arc)[::-1]]
)
edge += spiral[0] - taper_radius
taper = kdb.DPolygon([kdb.DPoint(z.real, z.imag) for z in edge])
for trans in [kdb.DCplxTrans.R0, kdb.DCplxTrans.R180]:
si.insert(half.transformed(trans))
si.insert(taper.transformed(trans))
return cell
if __name__ == "__main__":
import sys
delay_line()
layout.write(sys.argv[1])