analog-layout · diff

git:20260804.1353ef2 to git:20260831.7443919

101 added, 0 removed. Audit A to A.

---
name: analog-layout
description: Plan analog layout with matching, common-centroid, interdigitation, dummy devices, guard rings, and shielding. Use when the user says "analog layout", "matching layout", "common centroid", "dummies", "guard ring", "interdigitated", "current mirror layout", "differential pair layout".
---
# Analog Layout
`analog-sizing` picks W/L; this skill translates the sized schematic into a layout plan that actually survives process variation. Matching and noise immunity live or die on layout, not schematic.
## When to use
- Current mirrors, differential pairs, bandgap references, DAC ladders
- Any circuit where σ(Vth) or σ(β) matching drives the spec
- Circuits near digital noise sources (guard rings / shielding needed)
- Before handing off to a layout engineer or Magic / Virtuoso
## Inputs
1. Sized schematic with device list (W, L, M, NF)
2. Matching pairs / groups (which devices must match)
3. Sensitivity list (which mismatches kill which spec)
4. PDK rules (well spacing, latch-up, antenna)
5. Floorplan block size budget
## Workflow
1. **Identify matching groups** — pairs, quads, ratios
2. **Choose matching style**:
- Common-centroid 2×2, 4×4 for differential pairs
- Interdigitation ABAB or ABBA for current mirrors
- Centroid-preserving patterns for large ratios
3. **Dummy devices**: at least 2 dummies on each side of a matching row
4. **Guard rings**: n-well ring around PMOS, p-substrate ring around NMOS near digital
5. **Routing rules**:
- Symmetric routing for differential signals
- Shielded routing for sensitive nodes (clock, bandgap output)
- Avoid crossing a matched row with unrelated metal
6. **Density / antenna**: ensure fill rules and antenna rules met without breaking symmetry
7. **Post-layout extraction**: plan for RC back-annotation → re-simulate → may need sizing iteration
## Drawing a layout that actually closes A6 (measured recipe)
Everything above produces a *plan*. A6 (`analog_a6_block_pv_check`) does not grade plans: it
demands **DRC == 0 AND a netgen LVS match, per block**. `eda_analog_layout` cannot supply that —
it is honest that it returns `status: SCAFFOLD` because `readspice` + `gds write` places nothing.
So the layout is authored here, and three separate things have to be true. Each line below was
measured on sky130A (magic 8.3.679, device generator 1.0.599); the shape of the rule is
PDK-independent, the numbers are not.
**SCOPE — this recipe is for the devices that do NOT have to match.** Read it against the
Workflow above, not instead of it. §1 (one `nf=1` device per schematic MOS) and §2 (each device's
columns inside its own slot) make a common-centroid quad and an interdigitated mirror
*unconstructible*: both of those patterns exist precisely to INTERLEAVE the devices this recipe
keeps apart. The two are not in competition — they apply to different devices:
| the devices whose mismatch drives a spec | every other device in the block |
|---|---|
| Workflow §1-§5 above: common-centroid 2×2 / 4×4, ABAB / ABBA interdigitation, ≥2 dummies per side, guard ring, symmetric routing | §1-§2 below: one `nf=1` device per MOS, one slot each, generated two-layer routing |
| A6 LVS will NOT match with dummies in the layout — the dummy is a device the schematic does not contain. Take the waiver route (`/lvs-triage` **Batch waivers**: "some analog dummies intentionally don't appear in schematic — document waiver") and record the waiver id in `lvs_dummy_waiver`. | A6 LVS matches outright, which is what §1-§2 are optimised for |
**State which one you took, in a field.** A6's LVS compare is topology-only: it does not see a
centroid, does not see finger order, and dummies make it *harder*. So a block laid out as N
isolated devices closes A6 exactly as green as a fully matched one, and nothing in the tree tells
a reader which happened. Emit `analog/<block>/layout_matching.json` (schema under
**Output format** below). `"matching_style": "none"` is a legitimate, certifying answer — a level
shifter, a power switch and an ESD clamp have no matching group to build. What is not legitimate
is answering nothing: `analog_a5_layout_check` and `analog_a6_block_pv_check` both report the
class (`declared_matched` / `declared_none` / `undisclosed`) in their `--json` summary and on a
`MATCHING:` line, and A5 holds a disclosure that EXISTS to its own content
(`A5_MATCHING_GROUP_DUMMIES_INSUFFICIENT` is the ≥2-dummies rule above, executed;
`A5_MATCHING_DUMMIES_LVS_UNRECONCILED` is the waiver route, executed).
### 0. First check the netlist is layout-realizable at all
**An A3 netlist containing an ideal `V`/`I` source, or an ideal `R`/`C`/`L`, inside the block
subcircuit can never pass A6, no matter how well it is drawn.** The netgen PDK setup declares
device classes for the PDK's own `res_*` / `cap_*` subcircuits; it declares nothing that equates
an ideal SPICE primitive to a drawn device, so the compare cannot match. Measured: a block whose
netlist carried `Vbias nbias VSS 0.8`, `R1 VOUT FB 100k`, `Cc nd2 VOUT 3p` passed A3, A4 and A5
and made A6 unreachable **by construction** for three rounds.
**This is a gate, not a review item.** `analog_a3_netlist_gen_check` fails it as
`A3_NETLIST_IDEAL_PRIMITIVE_IN_BLOCK` — at A3, where the deck is written, not at A6 three steps
later where it reads as a layout defect. It is SPICE card grammar: `V`/`I` and the controlled and
behavioural sources `E`/`F`/`G`/`H`/`B` fire on the card letter alone; `R`/`C`/`L` fire only when
the value field holds a number or an expression, so `R1 a b <pdk_res_model> w= l=` and
`XR1 a b <pdk_device> W= L=` are both left alone. Cards at file scope and anything inside a
testbench subcircuit are out of scope — the testbench is where the fixes below SEND these
elements. The runner's own disclosed `deterministic_stub` deck is exempt.
Fix it upstream before drawing anything:
- ideal bias source → hoist to a **block port** the testbench drives (electrically identical);
- ideal R → the PDK poly/diff resistor with W/L solved for the same value;
- ideal C → the PDK MIM/VPP cap (arrayed if one instance is capped by the gencell's `lmax/wmax`);
- an ideal **load** element that the topology doc draws outside the block → move it to the
testbench, do not delete it.
Then **re-run A4** on the revised deck and quote the new corner numbers. A netlist change that
was never re-simulated is not a fix — and that is a gate too: a `corner_results.json` that
publishes `netlist_sha256` / `netlist_testbench_sha256` (as `analog_real_corner_sweep` does)
fails `analog_a4_corner_sweep_check` with `A4_SWEEP_STALE_VS_NETLIST` the moment either digest
stops recomputing from the file on disk. Every other subject-of-measurement rule at A4 is
answered once, when the artefact is written; this one is re-answered on every gate run.
### 1. One `nf=1` device per schematic MOS, with W = W_schematic x M
*(non-matched devices only — see SCOPE above. A matched group is interdigitated, and its finger
count is set by the pattern, not by this rule.)*
A multi-finger gencell exposes **per-finger pins** (`D0_0`, `G0_0`, `S1`…), and every one of them
extracts as a separate unconnected node — the single most common cause of
`Final result: Top level cell failed pin matching.` Setting `conn_gates 1` merges the *gates*
only and leaves the source/drain comb unstrapped, so it does not rescue the compare.
An `nf=1` gencell exposes exactly four clean ports: `B D S G`. And netgen expands `m=` into
parallel devices and merges them with `parallel {w add} {l critical}`, so a single wide finger is
LVS-equivalent to the schematic's multiplier. Verify that on your PDK in one command before
committing to it:
```
netgen -batch lvs "a.sp t" "b.sp t" <pdk>_setup.tcl out.log
# a.sp: X1 d g s b <nfet> W=4 L=1 m=4
# b.sp: X1 d g s b <nfet> W=16 L=1
# -> Final result: Circuits match uniquely.
```
For a very wide device, partition it into N parallel `nf=1` devices whose widths **sum** to
W x M (12 x W=60 for `W=6 m=120`). Disclose the partition in
`layout_matching.json`'s `device_partitions[]`, not only in prose: it is LVS-equivalent and it is
a real connected structure, but its parasitics are not those of an interdigitated device.
`analog_a5_layout_check` does the arithmetic — `A5_DEVICE_PARTITION_WIDTH_MISMATCH` when the
declared layout widths do not sum to `w_um x m`, because netgen merges parallel devices by
ADDING their widths and so sees the sum, not the intent. A declared layout device with `nf > 1`
is **recorded** (`multifinger_layout_devices`) and not failed: whether a multi-finger gencell
extracts with per-finger pins is a property of one PDK's device generator, which is what the
one-command check above is for.
### 2. A routing discipline that is DRC-clean by construction
*(non-matched devices only — see SCOPE above. "Each device's columns inside its own slot" is the
opposite of interdigitation, and a matched row routes symmetrically instead.)*
Do not hand-route. Use a two-layer discipline with a strict axis rule, so no two same-layer
shapes of different nets ever share a coordinate band:
- **met2 vertical only** — one unique column per device terminal, at the terminal's own x;
- **met3 horizontal only** — one unique rail per net, above every device;
- via1 at the terminal, via2 at the (column, rail) crossing;
- devices spaced far enough apart that each device's columns stay inside its own slot.
This is generated from the netlist, not drawn, and it makes column/rail collisions a
precondition check rather than a DRC round-trip.
### 3. The five PDK-specific facts that cost the most iterations
| symptom | cause | fix |
|---|---|---|
| `Via1 width < 52` / `via2 width < 56` / `via3 width < 0.32um` | magic's contact *type* carries the metal enclosure, so the **drawn** via must exceed cut + 2 x enclosure — not the cut size | draw via1/via2 >= 0.30 um, via3 >= 0.35 um |
| `Metal1 minimum area < 0.083um^2` on a short-L device | the gencell's gate met1 strap is tiny at minimum L, at **both** top and bottom | enlarge **both** gate straps, not just the one carrying the port label |
| gate contact too close to the source/drain straps | the S/D met1 straps run the full device height and end just below the gate strap | raise the gate pad **upward only**; keep `drc euclidean on` so the diagonal clearance counts |
| `MiM cap spacing to unrelated metal3 < 1.34um` | the cap's top plate lives on met4 over its own met3 bottom plate | escape the top plate on **met4 past the bottom plate's edge**, then drop met4 -> met3 -> met2 |
| `Width of RPM/URPM < 1.27um` on a bare resistor cell | the narrowest poly-resistor gencell variant is DRC-dirty **by itself** | use the next wider variant and re-solve L for the same resistance |
Also: the substrate/well **guard ring is the bulk terminal**. Hang a short local-interconnect
stub off the ring's own bar, put the li->met1 cut in the stub, and give it its own column — do
not try to contact the ring under the device's met1 straps.
### 4. Prove it, then prove what it licensed
```
magic: drc euclidean on; drc style drc(full); drc check; drc catchup; drc list count total
magic: extract do local; extract all; ext2spice lvs; ext2spice -o <top>_layout.spice
netgen -batch lvs "<top>_layout.spice <top>" "<block>_lvs_schematic.spice <top>" <pdk>_setup.tcl
```
Copy netgen's own transcript verbatim into `lvs.report` and magic's own count into `drc.report`.
Then remember what A6 was **holding back**. A7 and A8 are `PASS-VOIDED` while A6 fails; the
moment A6 goes green they become live PASSes on whatever evidence happens to be sitting there.
Measured: a `pre_vs_post.json` whose every `delta_pct` was `0.0` because the post values had been
copied from the pre values, and a hardmacro LEF/GDS streamed from the superseded unrouted layout.
Rebuild both from the routed layout:
- **A7** — `extract all` + `ext2spice cthresh 0` + `rthresh infinite` gives a real post-layout
netlist; simulate it with the **same** stimulus and the **same** `.meas` statements as A4.
If every delta is exactly zero, the numbers were copied — and both gates over
`pre_vs_post.json` now refuse it: a comparison whose every compared spec's post value equals
its pre value certifies only if it NAMES the post-layout artefact its post column was
simulated from **and that artefact resolves on disk**
(`A7_POSTSIM_ALL_ZERO_DELTA_UNEVIDENCED` in `analog_a7_post_layout_resim_check`,
`PRE_VS_POST_ALL_ZERO_DELTA_UNEVIDENCED` in `analog_pre_vs_post_layout_check`). Name the
extraction; a `0.0` column with nothing behind it is the one input that used to score better
than every honest comparison.
- **A8** — `gds write` + `lef write` from the routed cell. Two traps: magic's `lef write` frame
is the **magic cell bbox**, which can include magic-only layers (sky130 `pwell`) that stream no
GDS, so the LEF and GDS land in different frames — set an explicit `FIXED_BBOX` equal to the
GDS extent first. And magic's `lef write` emits **no `USE POWER` / `USE GROUND`**: re-attach
`DIRECTION` and `USE` to every pin. This one is also gated now — an abstract that types NO pin
at all is no longer read as an abstract with nothing to declare, and
`l21_macro_supply_rail_declared_check` recovers the typing from the macro's own Liberty
`pg_pin`/`pg_type` and from `L21.power_domains[]` before reporting the real rail finding, or
says the contract is UNVERIFIABLE (rule **L21-5**, waiver key
`l21_macro_lef_pin_use_absent_disclosed`). An untyped abstract used to SKIP with a message
byte-identical to a design that has no macro at all.
**Still unowned here, named rather than dropped.** Two of §4's claims are not yet programs, and
neither is silently assumed:
- **A7/A8 staleness against the routed layout.** The rules above catch a COPIED post column and
an UNTYPED abstract; neither catches evidence that was honestly measured against a
*superseded* layout. A4 has the equivalent rule (`A4_SWEEP_STALE_VS_NETLIST`) only because its
producer stamps a digest of its input; no A7 or A8 producer records the layout digest it was
built from, so the check has nothing to recompute against. It needs the producer change first
— do not substitute mtime, which a fresh checkout rewrites.
- **The `netgen -batch lvs` parallel-merge self-test in §1.** It is a real probe and it belongs
in a program, but it needs netgen and a PDK setup TCL inside the EDA container. A program that
cannot run its tool would either SKIP always — certifying nothing while looking like a gate —
or assert the answer it was meant to measure. Run the command by hand until it is wired to a
container-backed tool call.
## Output format
- `layout/<block>_layout_plan.md`:
- Device placement diagram (ASCII or SVG)
- Matching pattern per group
- Routing rules
- Dummy / guard-ring list
- Known risks
- `analog/<block>/layout_matching.json` — the same three facts as a FIELD, because A6's LVS
compare is topology-only and cannot tell a matched layout from an unmatched one. Read by
`analog_a5_layout_check` (rules) and `analog_a6_block_pv_check` (record). Every rule fires only
on a file that exists, so writing one costs nothing except having to mean it:
```json
{
"block": "<block>",
"matching_style": "common_centroid" | "interdigitated" | "none",
"matched_groups": [
{"name": "input_pair", "devices": ["Mn1", "Mn2"],
"style": "common_centroid", "dummies_per_side": 2}
],
"lvs_dummy_waiver": "<ticket>",
"device_partitions": [
{"schematic_device": "Mpass", "w_um": 6.0, "m": 120,
"layout_devices": [{"w_um": 60.0, "nf": 1}]}
],
"note": "free text"
}
```
| field | rule |
|---|---|
| `matching_style` absent / empty | `A5_MATCHING_DISCLOSURE_MALFORMED` |
| `"none"` with groups listed, or a style with no group | `A5_MATCHING_STYLE_GROUPS_CONTRADICT` |
| a matched group with `dummies_per_side` < 2, or none declared | `A5_MATCHING_GROUP_DUMMIES_INSUFFICIENT` |
| dummies declared, no `lvs_dummy_waiver` | `A5_MATCHING_DUMMIES_LVS_UNRECONCILED` |
| `layout_devices[].w_um` does not sum to `w_um x m` | `A5_DEVICE_PARTITION_WIDTH_MISMATCH` |
| omitted entirely | no rule — `undisclosed`, reported in both gates' summary and on the `MATCHING:` line |
## Technical basis
Classic references: Razavi "Design of Analog CMOS Integrated Circuits" ch. on layout, Hastings "The Art of Analog Layout", Pelgrom matching model. Process-specific matching coefficients come from the PDK.
## Handoff
- Sized devices → input to this skill came from `/analog-sizing`
- LVS verification → `/lvs-triage`
- DRC verification → `/drc-fix`
- Post-layout resim → `/ams-sim`
## Compliance gate (mandatory)
After producing your output, save it to a file and run:
```bash
python3 plugins/vibe-ic/_shared/skill_compliance_check.py \
--requirements plugins/vibe-ic/skills/analog-layout/compliance.yaml \
<your_output_file>
```
Exit 0 = PASS, exit 1 = FAIL with specific missing elements listed.
`compliance.yaml` in the corresponding skill directory enumerates
every required element of your output: section headers, metadata fields,
handoff lines, tool invocations.
**Your task is not complete until the audit returns PASS.** Missing
elements are the single largest source of skill-execution non-determinism
across different agents.
+ ## Captured by benchmark-enhancement-capture — 2026-09-01 (u_hawaii_adc A5 convergence: two blocks to netgen match-unique + KLayout DRC 0 on a native PDK)
+
+ The deterministic half of every rule below ships in
+ `programs/magic_gencell_layout_lib.py` (LAW numbers reference the campaign's
+ A5_STATUS.md in benchmark-data `uhadc/a5-layout-generator`, where the full
+ 24-law evidence trail and the working generator live). Use the library; do
+ not re-derive the parsing or the geometry checks by hand.
+
+ ### Skill: Magic .mag coordinate spaces — read `magscale` PER FILE, rlabels are always internal (LAWS #1/#22)
+
+ **Pattern**: `use` transforms and `rect` lines are in lambda ONLY when the
+ file has no `magscale` header; magic silently writes `magscale 1 2` the
+ moment any geometry sits off the lambda grid (a half-lambda column of an
+ odd-length device is enough), and then every transform/rect in THAT file is
+ 2x. Gencell children split by family (measured: MOS/resistor children carried
+ the header, MIM-cap children did not). `rlabel` coordinates are internal
+ (2x) in BOTH kinds of file.
+
+ **When to apply**: any scripted Magic layout — before trusting a single
+ parsed coordinate.
+
+ **What to do**: parse with `magic_gencell_layout_lib.mag_scale` /
+ `parse_use_transforms` / `parse_rlabels`. Never assume a family of .mag
+ files shares a unit.
+
+ **Worked failure**: reading a `magscale 1 2` parent as lambda doubled every
+ origin — the entire wiring layer painted off the devices; extraction
+ returned one single-pin net per terminal (51 nets, zero joins) with NO tool
+ error anywhere.
+
+ **Why this is GENERAL**: the header semantics are magic's own file format,
+ independent of PDK; any tech whose gencell emits off-grid geometry trips it.
+
+ ### Skill: exit-ladder discipline — stagger tied taps, allocate descents ascending AND obstacle-aware (LAWS #9/#23)
+
+ **Pattern**: a right-side exit ladder is short-proof only while rung y is
+ strictly ascending with rank ("a rung at Y_i can only cross a descent whose
+ top is below Y_i"). Two facts break a naive implementation: (a) D and S
+ labels of one MOS share a y, so two rungs go co-linear and short; (b) a
+ descent placed blindly at `origin + pitch*(rank-1)` ignores everything
+ between its tap and its rail — including other rows' tap pads.
+
+ **When to apply**: any tight-pitch cell whose contact columns cannot host
+ per-column vertical lanes (measured threshold: column pitch below wire
+ width + spacing + via pad).
+
+ **What to do**: `stagger_ladder_taps` (ties raised >= wire+space along the
+ column's own full-height M2 — LAW #9 guarantees the column carries it),
+ then `allocate_descent` per rank with every terminal and ladder point of
+ OTHER nets as obstacles. Afterwards run `cross_net_overlaps` on the wiring
+ manifest — an empty result is the necessary condition, not a formality.
+
+ **Worked failure**: 28 same-device cross-net shorts from co-linear rungs,
+ plus one macro's blind descent through a neighbour row's tap pads;
+ "Top level cell failed pin matching" was the only symptom LVS showed.
+
+ **Why this is GENERAL**: the proof obligation (ascending y, foreign-point
+ clearance) is geometry, not PDK; only the pitch numbers come from the deck
+ and they are parameters.
+
+ ### Skill: MIM-cap plate exits derive from the MEASURED plate bbox, never fixed offsets (LAW #24)
+
+ **Pattern**: the top-plate via stack auto-paints a pad on the bottom-plate
+ metal at its lowest level, so its lateral exit must clear the cap's own
+ bottom plate; the bottom-plate exit must clear the NEIGHBOUR's plate.
+ Fixed offsets are chip-luck: they encode one cap size.
+
+ **When to apply**: wiring any MIM/MOM cap whose plates are on routing
+ metals; sizing changed by even one micron re-opens this.
+
+ **What to do**: `cap_plate_exits(plate_bbox)` with the deck's clearances;
+ verify with `cross_net_overlaps` including the via3+/metal5 links.
+
+ **Worked failure**: three plate welds (two caps internally shorted, one
+ onto its neighbour) — found only by a flattened-layout island probe,
+ because `l2n.shapes_of_net` returns CHILD-LOCAL coordinates and an
+ unflattened weld analysis is garbage.
+
+ **Why this is GENERAL**: the auto-pad behaviour is the via-stack's, the
+ clearance is a deck number, and the bbox is measured per instance.
+
+ ### Skill: magic extraction is NOT connectivity-authoritative on gencell PDKs — sign off with the PDK's own LVS, quantize the comparison to the grid
+
+ **Pattern**: magic's extraction (hierarchical AND flat) does not reliably
+ merge parent-painted metal with gencell contact stacks (measured: 93
+ isolated terminal nets where KLayout found 9). Sign-off connectivity is the
+ PDK's KLayout LVS + netgen. At the comparison, the drawn mask is on the
+ manufacturing grid while derived netlist parameters may not be; the PDK
+ netgen deck declares a tolerance for this but the clause is inert in
+ current netgen builds (control-run measured). Quantize the COMPARISON copy
+ with `grid_snap_spice_params`; never touch the design netlist.
+
+ **When to apply**: every A5/A6 LVS on a Magic-gencell layout.
+
+ **why_not_bucket_a (the residual)**: WHICH netlist transform closes a given
+ mismatch (canonical net renaming, X-to-primitive conversion, parameter
+ bookkeeping strips) depends on the PDK's extraction dialect and stays a
+ per-PDK recorded recipe; the library ships the invariant pieces only.
+
+ _Captured by benchmark-enhancement-capture 2026-09-01 (u_hawaii_adc)._
+