PDK authoring guide

A PDK is a directory with one pdk.yml (the layer table, decks, suites, virtual layers and connectivity graph) plus a decks/ and suites/ subdirectory of rule YAML files. See IHP SG13G2 for a complete, working example to read alongside this guide.

Anatomy of pdk.yml

name: IHP SG13G2
version: "1.0"

suites:
  - name: main
    path: suites/main.yml
    description: Full DRC
  - name: precheck
    path: suites/precheck.yml
    description: Precheck subset

decks:
  - name: metal2
    path: decks/metal2.yml
    description: Metal 2

virtual_layers:
  - name: Pad
    op: union
    layers: [Passiv, Passiv.sbump, Passiv.pillar, dfpad]

connectivity:
  - connector: Cont
    layers: [Metal1]

layers:
  - name: Metal2
    gds_layer: 10
    gds_datatype: 0
  - name: Metal2.filler
    gds_layer: 10
    gds_datatype: 22
  - name: EdgeSeal
    gds_layer: 39
    gds_datatype: 0

Deck and suite paths are relative to the PDK file. name/version are free text, surfaced by gdscheck run at the top of its console output.

The layer table

layers: maps a name to a GDS (gds_layer, gds_datatype) pair. Rules, virtual-layer definitions and the connectivity graph all reference layers by name — the mapping to GDS numbers is resolved once, at PDK load time. A convention worth following (used throughout the bundled PDKs): name auxiliary datatypes of the same drawing layer with a dotted suffix, e.g. Metal2 (datatype 0), Metal2.filler (22), Metal2.mask (20), Metal2.pin (2) — it reads naturally in a rule’s layers: [...] list and keeps related entries visually grouped.

Decks

A deck (decks/<name>.yml) is a flat list of rules:

rules:
  - id: M2.a               # minimum width
    check: min_width
    layers: [Metal2]
    value: 0.20

  - id: M2.b               # minimum same-layer spacing
    check: min_space
    layers: [Metal2]
    value: 0.21

Each rule needs id, check (one of the names in Check reference), layers (at least one; a second layer turns most single-layer checks into an inter-layer check), and value (µm for widths/spaces, µm² for areas, % for densities — see the specific check’s reference page). params and text are optional, check-specific (see Rule parameters below). ignore names layers whose shapes a check should skip (e.g. excluding the seal ring from an inside_boundary check).

A rule id may repeat across multiple entries in the same deck (e.g. IHP’s TM2.b is both a min_space and a min_notch rule) — both fire under the same reported id, since KLayout-style report categories are keyed by id, not by list position.

Suites

A suite (suites/<name>.yml) imports rules from one or more decks by name, optionally restricted to a whitelist of ids (rules) and/or with a blacklist removed (exclude), without duplicating rule definitions:

include:
  - deck: metal1
    rules: [M1.a, M1.b, M1.j, M1.k]   # only these ids from metal1
  - deck: cont                        # no `rules:` → the whole cont deck
  - deck: metal2
    exclude: [M2.j, M2.k]             # the whole metal2 deck *except* these

rules is applied first, then exclude — so an include may combine both. An id in either list that doesn’t exist in the named deck is a load-time error (a suite typo can never silently drop a check, nor silently fail to drop one). An id that matches several entries in the deck (see Decks above) keeps or drops all of them together. A suite only selects rules — it can never override a rule’s value or params, which live solely in the deck.

Virtual layers

virtual_layers: declares derived layers computed from drawn (or other virtual) ones — see Virtual layer operations for the full operator reference and the eager/lazy evaluation trade-off. Each entry needs name, op and layers (the sources); mode: lazy, radius (for close/open/grow) and text (for with_text) are optional, op-specific. A virtual layer is assigned a synthetic GDS layer number automatically (starting at 30000) and can be referenced by rules exactly like a drawn layer.

The connectivity graph

connectivity: declares how net extraction bridges layers, for the net-aware checks (antenna_ratio, gate_connected_min_area) — see Architecture for how extraction works. Each entry is a connector layer (a via or contact) and the conductor layers it joins where it overlaps them:

connectivity:
  - connector: Cont
    layers: [GatPoly]
  - connector: Cont
    layers: [Activ]
  - connector: Cont
    layers: [Metal1]
  - connector: Via1
    layers: [Metal1]
  - connector: Via1
    layers: [Metal2]

A conductor layer’s own connected regions don’t need a graph entry — lateral routing on one layer is already resolved by region stitching; only the vertical via/contact stack needs declaring. If a PDK has no net-aware checks in any of its decks, connectivity: can be omitted entirely (net extraction never runs).

Deriving a process with extends

name: IHP SG13CMOS5L
extends: ../ihp-sg13g2/pdk.yml
decks:
  - name: cont
    path: decks/cont.yml
suites:
  - name: main
    path: suites/main.yml

extends (a path relative to this file) inherits the base PDK’s layers and virtual_layers — this file’s own entries are appended after them, and a virtual_layers entry with the same name as one in the base replaces it (the child’s definition wins). Everything else — decks, suites, connectivity — is never inherited; a derived process states its own deck list and connect graph explicitly, even if it reuses most of the base’s rules. One level only: the base file may not itself extend another.

This is the pattern for a process variant that shares most of a foundry’s device and recognition layers but has its own rule set (a different metal stack, different design rules, or — as with SG13CMOS5L — a restricted set of forbidden layers).

Rule parameters

params: is a flat map of string -> number — every check-specific numeric knob beyond the universal value lives here (e.g. window for the windowed-density checks, boundary_layer for the seal-ring-aware density checks, rows/cols for array-spacing checks). The exact keys a given check reads, with their defaults, are listed on that check’s reference page under Parameters — params the check doesn’t recognise are silently ignored, so a typo’d param name fails quietly rather than erroring; double-check the reference page’s exact key spelling.

text: is a separate, sibling field (not inside params, since params only carries numbers) for the handful of checks that need a text/label pattern — forbidden_unless_labeled is the only current user.

Validating a new PDK

There’s no PDK-specific test harness beyond what gdscheck itself gives you:

  1. gdscheck list-decks/list-suites --process <path-to-pdk.yml> to confirm every deck and suite loads and parses.

  2. gdscheck show-deck --process <path> --deck <name> for each deck, to eyeball every rule’s resolved layers/value/params before running it on a real layout.

  3. Run each deck (or the full main suite, if defined) against a real design and sanity-check the violation counts against a reference DRC engine for at least a few rules — see Contributing (Validating against the reference KLayout deck) for the workflow this project itself uses to cross-check new/changed checks.

  4. If the PDK is meant to ship in-tree, follow Contributing for the test-fixture generator convention (a synthetic pass/fail GDS pair per rule, asserted by an integration test) rather than relying solely on real-design spot checks.