Core Concepts
The mental model behind Rosette. Cells, layers, ports, geometry, and routing.
This page is the single spot where the core ideas of Rosette are introduced. If you have used other layout tools before, most of this will feel familiar. Skim the headings and focus on the naming and quirks. If this is your first layout tool, read top-to-bottom and keep the API Reference open in another tab.
Everything below is about the Python API. Units are micrometers (um) and angles are degrees.
Cells, instances, and libraries
A Cell is the basic unit of a design. It is a named container that holds polygons, paths, text labels, ports, and references to other cells. Cells are how you break a chip up into reusable pieces: a grating coupler is a cell, a ring resonator is a cell, and the whole chip is a cell that references the other two.
from rosette import Cell, Layer, Point, Polygon, write_gds
inner = Cell("ring")
inner.add_polygon(Polygon.rect(Point(0, 0), 20, 2), Layer(1, 0))
top = Cell("top")
top.add_ref(inner.at(0, 0))
top.add_ref(inner.at(0, 30))
write_gds("output.gds", top)An Instance is a placed cell: a cell
plus a transform (translation, rotation, mirror). You create instances
with cell.at(x, y) and add them to a parent with parent.add_ref(...).
Chained calls apply in order, so cell.at(10, 0).rotate(90) first
translates to (10, 0) and then rotates around the origin, landing
the cell at (0, 10). See the
Instance docs for the full transform
order rules.
For large regular grids, use Instance.array
or ArrayCopy. One compact GDS array
reference replaces thousands of individual refs.
A Library is a collection of cells that
travel together when reading or writing GDS. In most designs you never
construct a Library directly: write_gds
auto-collects child cells from a top-level Cell built with Instance
references.
When to make a sub-cell
If you are going to place the same shape more than once, make it a cell. Hierarchy keeps your GDS small, your viewer fast, and your intent clear. Merging everything into one giant cell works, but it scales badly.
Layers
A Layer is a (number, datatype) pair that
GDS uses to separate materials: silicon, metal, doping, text, and so on.
The canonical way to construct one is:
from rosette import Layer
Layer(1, 0) # number=1, datatype=0
Layer(1) # datatype defaults to 0Other accepted forms
Anywhere a Layer is expected (e.g. cell.add_polygon), a bare int
like 1 or a tuple like (1, 0) is also accepted and coerced. Prefer
the explicit Layer(...) constructor in design scripts. It reads more
clearly and is the form the rest of these docs use.
For real designs you will want to name your layers. Define them in
rosette.toml under [layers]:
[layers.silicon]
number = 1
datatype = 0
color = "#ff69b4"
description = "Silicon waveguides"
[layers.text]
number = 10
datatype = 0
color = "#607d8b"Then load them in Python with
load_layer_map:
from rosette import Cell, Point, Polygon, load_layer_map
layers = load_layer_map() # reads rosette.toml
cell = Cell("wg")
cell.add_polygon(
Polygon.rect(Point(0, -0.25), 50, 0.5),
layers.silicon.layer, # Layer(1, 0)
)
print(layers.silicon.color) # "#ff69b4"load_layer_map() returns a LayerMap
whose entries are LayerInfo objects.
Each exposes the Layer, a color, and metadata used by the viewer.
Semantic names everywhere
Everywhere Rosette asks for a layer (add_polygon, DRC config, DFM
config, and so on) you can use the semantic name you defined in
[layers] instead of the raw number/datatype. Keep design scripts
readable, keep foundry numbers in one place.
Ports
A Port is a named connection point on a cell. It has a position, an outward-facing direction (unit vector), and an optional width.
from rosette import Cell, Layer, Point, Polygon, Port, Vector2
cell = Cell("wg_segment")
cell.add_polygon(Polygon.rect(Point(0, -0.25), 100, 0.5), Layer(1, 0))
cell.add_port(Port("in", Point(0, 0), Vector2(-1, 0), width=0.5))
cell.add_port(Port("out", Point(100, 0), Vector2( 1, 0), width=0.5))Ports do three jobs:
- Routing anchors. The
RouteAPI uses ports as start and end points. Seestart_at_portandend_at_port. The route departs and arrives along the port's direction, at the port's width. - Connectivity checks.
rosette check(the design-checks feature) flags ports that are supposed to connect but don't: typos in routing, width mismatches, angle mismatches. - Programmatic placement.
cell.place_at_portis a lower-level helper that returns aCellRefaligning one of a child cell's ports to a target port. For most designs, prefer composing ports withInstanceandRouteinstead.
Top-level ports are treated as external I/O and are not flagged as unconnected. Use them to mark the chip's inputs and outputs.
Geometry primitives
You rarely touch these directly (Polygon is the one you will use most),
but every other API is built on top of them.
Point(x, y): a 2D point. UsePoint.origin()for(0, 0).Vector2(x, y): a 2D vector, used for port directions and array lattice vectors.Polygon: a closed polygon. Build one withPolygon.rect(origin, width, height),Polygon.rect_centered(center, width, height), or pass a list of points.BBox: an axis-aligned bounding box, returned bycell.bbox()andLibrary.cell_bbox(name).Transform: a 2D affine transform (translate + rotate + scale + mirror). Instance transforms are composed from these under the hood.
If you need a tapered ribbon along a centerline, skip manual polygon
construction and reach for
offset_polygon or
offset_polygon_varying.
Routing
A Route connects a sequence of waypoints
with straight segments, inserting bends at corners and tapers at width
changes. It is not an auto-router. You supply the waypoints. A
minimal route between two ports looks like:
from rosette import Cell, Layer, Point, Port, Route, Vector2
src = Port("out", Point(0, 0), Vector2( 1, 0), width=0.5)
dst = Port("in", Point(120, 30), Vector2(-1, 0), width=0.5)
route = Route(Layer(1, 0), width=0.5, bend_radius=5.0)
route.start_at_port(src)
route.to(60, src.position.y)
route.to(60, dst.position.y)
route.end_at_port(dst)
top = Cell("top")
top.add_ref(route.to_cell("wg"))The full story (Euler vs. circular bends, taper control, common pitfalls) lives in the Routing guide.