Language Reference

Cross-Sections

Define the layers across a waveguide once (core, slab, cladding, heater, electrodes) and draw any waveguide shape with them; ports take their cross-section from the waveguide ends.

A cross-section describes a waveguide or a wire across its width: which layers it has, how wide each one is, and where it lies relative to the centerline. Define it once, then give it where a waveguide primitive expects a width. Every section is drawn along the same centerline, each on its own layer.

define xsection rib(width = 700nm, slab = 2um) {
    si   width
    slab width + 2 * slab
    with min_radius = 10um
}

cell Bend {
    shape euler with min_radius = 10um, width = rib, angle = 90deg as b.start at self.origin
}
Compiling…

The examples on this page are whole files, compiled in your browser after a layer table with the layers si, slab, metal and clad. The drawing shows the file's top cell.

Defining a cross-section

define xsection name(parameters) {
    layer width [offset distance]
    ...
    with property = value, ...
}
  • Parameters are written like a cell's: width = 500nm takes its type from the default, and n integer is a parameter without one. Expressions in the definition can use the parameters and global constants.
  • Sections. Each line is a section: a layer, a width, and optionally the offset of the section's middle from the centerline. Positive offsets lie to the left of the direction of travel. A list of offsets draws one section per offset. A section of zero width is left out, so a parameter can switch a heater or a cladding off.
  • The main section is the first one. Its width is the cross-section's width and its layer is the layer of the ports. A shape's anchors and its role in shape operations come from the main section, so adding a cladding never moves a placement.
define xsection gsg(signal = 10um, ground = 20um, gap = 5um) {
    metal signal
    metal ground offset [(signal + ground) / 2 + gap, -(signal + ground) / 2 - gap]
    with type = "electrical"
}

cell Electrodes {
    shape straight with 60um, gsg as line.start at self.origin
}
Compiling…

Properties are set with with, as in markers:

PropertyTypeDefaultMeaning
typestring"waveguide"The type of ports with this cross-section, compared by the connection checks.
min_radiussizenoneThe tightest bend allowed. Every shape drawn with the cross-section is checked against it, on the exact curve.
radiussizefrom min_radiusThe bend radius for routing: min_radius for "arc" bends, the smallest effective radius an Euler bend with that minimum radius has for "euler".
bendstring"arc"The bends of routes: "arc", "euler" or "corner" (sharp corners, for wires).
pnumber0.5The Euler fraction of "euler" bends.
pitchsizethe total widthThe spacing of bundled routes. The default is the width across all sections, so that neighbors just touch.

radius, bend, p and pitch are for automatic routing, which is being designed; for now they can be read as members.

Cross-section values

  • Names and parameters. rib is the cross-section with all parameters at their defaults. rib(1um) and rib(width = 1um, slab = 3um) set parameters by position or by name. Calling any cross-section value changes some of its parameters: xs(width = 2um).
  • Type. The type is xsection. Cells can take cross-sections as parameters; each distinct cross-section makes a distinct cell, like any other parameter value.
  • Members. width, type, min_radius, radius, bend, p and pitch, and the parameters, e.g. rib.slab.
  • Equality. == is true for the same definition with the same parameters.
define xsection strip(width = 500nm) {
    si   width
    clad width + 4um
    with min_radius = 5um
}

cell Straight(length = 10um, xs = strip) {
    marker {
        input  { at wg.start }
        output { at wg.end }
    }
    shape straight with length, xs as wg.start at self.origin
}

cell Two {
    place Straight at self.origin as a
    place Straight with xs = strip(1um) at a.marker.output[0]
}

Two has a 500 nm and a 1 µm strip waveguide, each in its cladding; the second is the cell Straight$… with xs = strip(width = 1um).

Drawing with a cross-section

The width of path, straight, arc, euler, sbend, bezier, ring and racetrack, and both widths of taper, can be a cross-section.

  • Where. A shape drawn with a cross-section gets its layers from it, so it is written outside layer blocks: in the cell body, in place blocks, or in for and if there. In a layer block it is an error, and a shape whose width is a size still belongs in one. clone follows the same rule.
  • Geometry. Each section runs along the centerline at its offset. Rings and racetracks run counter-clockwise, so positive offsets lie towards the center.
  • Checks.
    • Every section must fit its bends: the bend radius must exceed the offset plus half the width.
    • A cross-section's min_radius is checked against the exact tightest radius of each shape: the smallest radius of curvature of an Euler bend, the whole curve of S-bends and Bézier curves, and the bend_radius of a path. A path with sharp corners fails the check.
define xsection heated(width = 500nm, heater = 2um) {
    si    width
    clad  width + 4um
    metal heater
    with min_radius = 5um
}

cell HeatedRing {
    shape ring with 10um, heated as ring
    place ring at self.origin
}
Compiling…
define xsection rails(gap = 4um) {
    metal 1um offset [gap / 2, -gap / 2]
    with type = "electrical"
}

cell Wires {
    shape path with [(0, 0), (20um, 0), (20um, 15um)], rails, bend_radius = 5um as p
    place p at self.origin
}
Compiling…

straight is a primitive for this purpose: straight(length, width) runs from the origin towards +x and has the anchors start and end. With a size as its width it is a rect with those anchors.

Tapers between cross-sections

taper with two cross-sections changes one into the other. Each section's width follows the taper's profile, and its offset changes linearly.

  • Pairing. Sections are paired by layer: the k-th section on a layer at the start with the k-th one on that layer at the end.
  • Unpaired sections. A section found at only one end narrows into, or grows out of, the main section of the other end.
  • Main sections. Both cross-sections must have their main sections on the same layer.
  • Anchors. start carries the first cross-section and end the second.
define xsection strip(width = 450nm) { si width }

define xsection rib(width = 700nm) {
    si   width
    slab width + 4um
}

cell Converter {
    shape taper with 15um, rib, strip as t.start at self.origin
}
Compiling…

Ports

  • Inferred. When every location of a marker is the start or end of a shape drawn with a cross-section, or a port of an instance that has one, and all have the same cross-section, the marker takes it.
  • Given. with xsection = rib sets the cross-section explicitly.
  • Width and type. A port with a cross-section gets width (the main section's width) and type from it, unless they are given.
marker input (0,0,0deg) {xsection=rib, width=700nm, type="waveguide"}

laylight netlist and laylight lvs report connected ports whose cross-sections differ, as they report differing widths and types:

error: b.input[0] (cross-section strip(width = 450nm)) is connected to a.output[0] (cross-section strip)

Sections in shape operations

  • Operands. In boolean and morphological operations, a shape drawn with a cross-section stands for its main section.
  • Other sections. wg[clad] is its sections on the layer clad.
  • Replacement. The same-layer rule works per section: a derived shape replaces the operand sections on its own layer, and the shape's other sections stay.
define xsection strip(width = 500nm) {
    si   width
    clad width + 3um
}

cell Pair {
    shape straight with 20um, strip as top.start at (0, 1um)
    clone top as bottom.start at (0, -1um)
    layer clad { shape top[clad] + bottom[clad] as cladding }
}
Compiling…

The two claddings become one region, and the cores stay as they are.

Reference: equivalents in other tools

Laylightgdsfactorygdstk
define xsection with one line per sectiongf.cross_section.cross_section(width, layer, sections=[gf.Section(width, layer, offset)])FlexPath(points, width=[...], offset=[...], layer=[...])
with min_radius = 5umradius_min = 5—
shape straight with length, xsgf.components.straight(length, cross_section=xs)FlexPath(...).segment(...)
shape euler with radius, xs, 90deggf.components.bend_euler(radius, cross_section=xs)—
shape taper with length, xs1, xs2gf.path.transition(xs1, xs2) with gf.path.extrude_transition—

gdsfactory's section offsets are positive to the right of the direction of travel, Laylight's to the left, so their signs change. Auto Routing maps gdsfactory's routing functions.

On this page