Virtual layer operations¶
Derived layers computed from drawn layers, declared in pdk.yml (virtual_layers:) and referenced by rules like any drawn layer.
Global vs. lazy evaluation¶
Each entry in virtual_layers: takes an optional mode:
virtual_layers:
- name: Pad
op: union
layers: [Passiv, Passiv.sbump, Passiv.pillar, dfpad]
- name: TopMetal2NotIND
op: difference
mode: lazy
layers: [TopMetal2, IND]
Eager (default, no ``mode:``) — computed once, up front, right after the top cell is flattened (
pdk.rs→compute_virtual_layers), as an ordinary set of boundaries inserted into the flattened layout under the virtual layer’s synthetic(gds_layer, gds_datatype). Every downstream check reads it exactly like a drawn layer, with no further cost. Only five ops are supported eagerly:union,intersection(and),difference(not),inside, andclose.Lazy (``mode: lazy``) — registered with the tiled merge cache instead: a synthetic key built per tile, on first use, from its (recursively resolved) source layers’ tiles. Costs nothing until a rule actually needs it, and its memory profile is the same tile+halo bound as a drawn layer — required for any derivation chain that touches a dense, chip-wide layer (
Activ,GatPoly, metals), since eager evaluation unions the whole chip in one shot and does not scale to those. All fifteen ops below are available lazily; only the five above are available eagerly.
A rule can only reference a lazily-evaluated layer once the deck actually runs a check against it — see Troubleshooting & FAQ if a lazy virtual layer produces no results or an unexpected empty layer.
union¶
The union of all source layers. Duplicate polygons contributed by more than one source (e.g. two layers that happen to carry the same pad shape) are only counted once.
Eager and lazy.
intersection¶
The geometric AND of all source layers — empty wherever any source layer is empty. Used
for device-recognition layers (e.g. CuPillarPad = Passiv.pillar AND dfpad). YAML
alias: and.
Eager and lazy.
difference¶
The first source layer minus every other source layer (e.g.
ContNoSealring = Cont NOT EdgeSeal). YAML alias: not.
Eager and lazy.
interacting¶
Lazy only. Keeps whole regions of layers[0] that touch (overlap) any region of
layers[1], region membership resolved by stitching each source’s tile-local pieces
into whole connected regions first (see Architecture) so a region is kept or
dropped as a unit, never split by a tile boundary. Mirrors KLayout’s
Region#interacting.
not_interacting¶
Lazy only. The complement of interacting: keeps whole regions of layers[0] that
touch no region of layers[1]. Mirrors KLayout’s
interacting(..., inverted: true).
covering¶
Lazy only. Keeps whole regions of layers[0] that fully contain a region of
layers[1]. Computed the same way as interacting — every real use in the bundled
PDKs has layers[1] ⊆ layers[0] already, where “covers” and “interacts” coincide.
grow¶
Lazy only. One-directional morphological dilation by radius µm (the def’s radius
field). Only grows outward — pair with not_interacting/interacting downstream if
you need the grown reach purely as a proximity test.
close¶
Morphological closing (dilate then erode) by radius µm: regions whose gap is under
2 × radius merge into one. Used for same-net merging (e.g. NWell tie regions closer
together than the rule distance). A closed pair of regions that stays separate keeps its
true outer edges, so a downstream min_space still measures the real gap between the
(now-merged) regions correctly.
Eager and lazy — the only morphological op available eagerly.
open¶
Lazy only. Morphological opening (erode then dilate) by radius µm: removes every part
of a region narrower than 2 × radius. A region that vanishes entirely had no spot
wider than that anywhere — pairing open with a downstream not_interacting against
the opened layer implements “minimum width somewhere” style rules.
holes¶
Lazy only. Unary: the hole area of each source region, as filled polygons (KLayout’s
.holes) — e.g. the interior of a substrate-tie ring. Declare the def’s radius as
the maximum expected ring extent: a hole only materialises correctly in a tile whose
bucket assembles the whole ring, so this only works for device-scale rings, not a
chip-perimeter ring (whose “hole” is the entire die and is not tile-local).
with_holes¶
Lazy only. Unary shape filter: keeps source regions that contain at least one hole
(KLayout’s .with_holes) — e.g. a ring-shaped NWell encircling an iso-PWell. Same
tile-locality limit as holes.
with_text¶
Lazy only. Keeps whole regions of layers[0] containing a text label on layers[1]
(a TEXT layer) matching the def’s text pattern — a trailing * makes it a
case-insensitive prefix match, otherwise a case-insensitive exact match. Reads the
layout’s text/label records directly rather than any layer’s polygon tiles. Mirrors
KLayout’s interacting_with_text.
square¶
Lazy only. Unary shape filter: keeps regions that are (approximately) a single square.
not_square¶
Lazy only. The complement of square.
not_circle¶
Lazy only. Unary shape filter: keeps regions that are not a (approximately) regular circle — e.g. flagging disallowed bond-pad shapes.
not_circle_or_octagon¶
Lazy only. Like not_circle, but also excludes regular octagons.
inside¶
Eager only, two layers exactly: layers[0] (the target) and layers[1] (a ring).
The part of the target that lies within the area enclosed by the ring — the ring’s own
holes are filled first (so a seal frame becomes “frame + interior”), since there is
usually no drawn layer for that interior region. Both sources must be real (drawn)
layers; inside does not support one virtual layer feeding another.