Design rule checking
Configure DRC rules in rosette.toml and catch foundry violations before tapeout.
Design rule checking (DRC) catches geometry that will not fabricate
correctly: features that are too narrow, polygons that are too close
together, illegal edge angles, overlapping shapes on a single-patterning
layer, and so on. Every foundry publishes a DRC deck you must pass before
taping out. Rosette lets you encode a subset of those rules in
rosette.toml and check them from Python or the CLI.
See Project configuration for the complete
rosette.toml schema, including layers, DFM, design checks, and snapshots.
When to run DRC
- While you design, to catch mistakes early: run
uv run rosette build designs/my_design.py --checkorrun_drcin your script. - Before tapeout, to enforce the full deck: run
uv run rosette drcand gate your build on a clean result. Foundry tools will still be the final source of truth, but Rosette catches most of the cheap mistakes.
Configure rules in rosette.toml
DRC lives under the [drc] table. Two shapes of rule:
- Per-layer rules live in
[drc.layers.<name>], one table per layer, with one key per rule. - Inter-layer rules live in
[[drc.rules]], an array of tables, each with atypeand rule-specific fields.
Layer keys accept either a semantic name from [layers] (recommended) or
the traditional "number/datatype" format. For example,
[drc.layers.silicon] and [drc.layers."1/0"] are equivalent.
# rosette.toml
[layers.silicon]
number = 1
datatype = 0
[layers.p_doping]
number = 20
datatype = 0
[layers.n_doping]
number = 21
datatype = 0
# Per-layer rules on the silicon waveguide layer.
[drc.layers.silicon]
min_width = 0.12 # minimum feature width (um)
min_spacing = 0.13 # minimum same-layer spacing (um)
min_area = 0.01 # minimum polygon area (um^2)
angles = [0, 90] # allowed edge angles (degrees)
no_overlap = true # forbid overlapping polygons on same layer
no_self_intersection = true
# Inter-layer rule: keep P+ and N+ apart.
[[drc.rules]]
type = "spacing"
layer1 = "p_doping"
layer2 = "n_doping"
min_spacing = 0.50
name = "PN_SPC"
[[drc.rules]]
type = "forbid_overlap"
layer1 = "p_doping"
layer2 = "n_doping"
name = "PN_NOOVLP"Supported per-layer rules
| Key | Meaning |
|---|---|
min_width | Minimum feature width, in um. |
max_width | Maximum feature width. |
min_spacing | Minimum same-layer spacing between polygons. |
min_area | Minimum polygon area, in um^2. |
min_edge_length | Shortest allowed polygon edge. |
angles | Allowed edge angles in degrees (e.g. [0, 45, 90, 135]). |
acute_angle | Minimum allowed convex interior angle in degrees. |
snap_to_grid | Manufacturing grid pitch in design units (for example, 0.001 for 1 nm). |
no_overlap | Forbid overlapping polygons on the same layer. |
no_self_intersection | Forbid self-intersecting polygons. |
density | Grouped subtable defining density bounds, window size, step, and optional region layer. |
Supported inter-layer rules
Each [[drc.rules]] entry has a type and either a (layer1, layer2)
pair or an (inner, outer) pair. All accept an optional name used in
violation messages.
| Type | Fields | Meaning |
|---|---|---|
spacing | layer1, layer2, min_spacing | Minimum inter-layer spacing. |
enclosure | inner, outer, min_enclosure | Inner layer must be enclosed by outer layer. |
require_overlap | layer1, layer2 | Two layers must overlap. |
forbid_overlap | layer1, layer2 | Two layers must not overlap. |
not_inside | inner, outer | Inner layer must not sit fully inside outer layer (keep-out zone). |
Near-threshold warnings
The top-level [drc] table accepts warning_margin in design units,
typically microns. Length-based numeric violations within that absolute margin are downgraded to
warnings instead of errors. This is useful while you iterate on a design
and don't want to treat a 0.115 um wire (spec: 0.12) as a tapeout-blocking
error.
[drc]
warning_margin = 0.01 # within 0.01 um of a length threshold -> warningSee DrcRules.warning_margin for details.
Run DRC from Python
from rosette import Cell, Layer, Point, Polygon
from rosette.drc import load_drc_rules, run_drc
cell = Cell("top")
cell.add_polygon(Polygon.rect(Point(0, 0), 10, 0.08), Layer(1, 0)) # too narrow
rules = load_drc_rules() # reads rosette.toml
result = run_drc(cell, rules)
if result.passed:
print(
f"DRC clean ({result.polygons_checked} polygons, "
f"{result.rules_checked} rules)"
)
else:
print(f"{result.error_count} error(s), {result.warning_count} warning(s):")
for v in result.violations:
(x0, y0), (x1, y1) = v.bbox
print(
f" [{v.severity}] {v.rule_name or v.rule_type}: {v.message} "
f"@ ({x0:.3f}, {y0:.3f})-({x1:.3f}, {y1:.3f})"
)run_drc returns a
DrcResult. Check result.passed for
a quick pass/fail, and iterate result.violations for details. Each
DrcViolation carries rule_name,
rule_type, severity, a human-readable message, and a bbox
(((x0, y0), (x1, y1)) in um) pointing at the offending geometry so you
can render it in the viewer or jump to it by coordinate.
Cells assembled with public Instance placements track their children, so
run_drc can auto-collect the hierarchy. For a cell obtained from an imported
or explicitly managed hierarchy, pass its
Library via library= so DRC can resolve all
referenced cells.
Explicit skips and waivers
Use a DrcPolicy when a run should trust an
entire cell subtree or waive an intentional violation in a bounded region.
Policy is supplied explicitly to run_drc; it is not stored on Cell.
from rosette import BBox, Point
from rosette.drc import DrcPolicy, run_drc
policy = DrcPolicy()
policy.skip_cell("trusted_pdk_cell")
policy.waive_region("taper", BBox(Point(9, -1), Point(11, 1)))
result = run_drc(cell, rules, policy=policy)skip_cell(name) suppresses a violation only when every named source cell is
inside that skipped subtree. Violations without cell provenance, including
density-window violations, remain visible. waive_region(name, bbox) uses the
named cell's local coordinates and requires full containment; Rosette
transforms the region for each placement before filtering.
result.suppressed_violations, result.skipped_cells, and
result.waived_violations report effects of the explicit policy for that run.
If both a cell skip and a waiver match, the violation counts as suppressed by
the cell skip rather than being counted twice.
Run DRC from the CLI
Three CLI entry points hit DRC:
# Just DRC, against the cell defined in the script.
uv run rosette drc designs/my_design.py
# All enabled design checks (DRC, connectivity, bend radius, ...).
uv run rosette check designs/my_design.py
# Build to GDS with a DRC pre-check. The build always proceeds; DRC
# results are printed to stderr.
uv run rosette build designs/my_design.py --checkBoth uv run rosette drc and uv run rosette check accept --json to emit a single
machine-readable object on stdout instead of prose — see the
agent workflows guide.
--check does not gate the build
uv run rosette build --check runs DRC and reports violations, but it still
writes the GDS file. If you want a hard gate for CI, run
uv run rosette drc and check the exit code, or call run_drc in your script
and raise on result.passed == False.
See also
DrcRules: construct and inspect rule sets programmaticallyDrcPolicy: explicit per-run cell skips and local region waiversDrcResult: result aggregate with statistics andpassedflagDrcViolation: individual violationsload_drc_rules/run_drc: the main entry points- Core concepts: layer, cell, and port fundamentals