From 1c134a1fb0f105ff0b0beea417e447ec0eabc9cd Mon Sep 17 00:00:00 2001 From: euler Date: Sat, 22 Aug 2026 11:43:05 -0500 Subject: [PATCH] mimcap: size the via array off the MIM's enclosure, not the metal's `mimcap` draws FuseTop at `size` and hands `size` straight to `via_array`, which decides how many via columns fit using the generic metal-over-via enclosure -- 0.12um for met5 over via4 on gf180. The MIM plate has a stricter rule: MIMTM.4 (option B) and MIM.4 (option A) both want 0.4um of plate over the via that contacts it. The plate-to-via margin is therefore a sawtooth in `size`: it grows until one more column fits, then drops back. 5.00um 5 columns margin 0.770 5.03um 5 columns margin 0.785 5.05um 6 columns margin 0.395 <- column enters, margin drops 5.80um 6 columns margin 0.770 5.85um 7 columns margin 0.395 The floor of that sawtooth is 0.390um: ample for 0.12, and 10nm short of 0.4. So the violations do not scale with the size, they repeat with the via pitch of 0.80um, and a generator picking the plate side from a target capacitance lands on them regularly while a hand-picked round size does not. Shrinking the array region by the difference between the two rules lifts the whole sawtooth by that amount, floor included: 0.390 -> 0.670um. The rule goes in the PDK as `capmet.via_enclosure`; PDKs that do not declare it keep the old sizing untouched. Cost: a column enters later than it used to, so some sizes get one fewer via row and column -- 5.05um goes from 6 to 5. That is more series resistance into the plate, which a MIM can afford, and it buys an enclosure that holds at every size instead of at most of them. Verified on gf180, sweeping 5.00-8.00um in 0.01um steps and measuring the FuseTop-to-via4 distance in the GDS rather than counting violations: before 301 sizes, 5 below 0.4um (5.05 5.85 6.65 7.44 7.45), worst 0.390 after 301 sizes, 0 below 0.4um, worst 0.670 DRC agrees on the nine sizes it was run for: 24 MIMTM.4 at 5.05um and 28 at 5.85um before, 0 everywhere after. A 14-case cell sweep that had 48 MIMTM.4 in one case now passes all of them. --- src/glayout/pdk/gf180_mapped/gf180_grules.py | 6 ++++- src/glayout/primitives/mimcap.py | 27 +++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/glayout/pdk/gf180_mapped/gf180_grules.py b/src/glayout/pdk/gf180_mapped/gf180_grules.py index 2d8391ce..ad01475c 100644 --- a/src/glayout/pdk/gf180_mapped/gf180_grules.py +++ b/src/glayout/pdk/gf180_mapped/gf180_grules.py @@ -364,5 +364,9 @@ grulesobj["capmet"]["met4"] = {} grulesobj["capmet"]["via4"] = {} grulesobj["capmet"]["met5"] = {} -grulesobj["capmet"]["capmet"] = {'capmettop': (81, 0), 'capmetbottom': (46, 0), 'min_separation': 1.2} +# via_enclosure: the MIM plate has its own, stricter enclosure of the via that +# contacts it -- 0.4um (MIMTM.4 / MIM.4). The generic metal-over-via rule is +# 0.12, so a via array sized off that reaches 0.28um too close to the plate +# edge and the cap fails DRC at sizes where the count happens to land there. +grulesobj["capmet"]["capmet"] = {'capmettop': (81, 0), 'capmetbottom': (46, 0), 'min_separation': 1.2, 'via_enclosure': 0.4} diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index 5d6e24d2..f644c283 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -144,8 +144,33 @@ def mimcap( # 3. Create top metal plate with via connections # Use minus1=False to get maximum via coverage like KLayout generator + # via_array decides how many via columns fit using the generic + # metal-over-via enclosure -- 0.12um for met5 over via4 on gf180 -- but the + # MIM plate has a stricter one: MIMTM.4 (option B) and MIM.4 (option A) + # both want 0.4um of plate over the via that contacts it. + # + # The resulting plate-to-via margin is a sawtooth in `size`: it grows until + # one more column fits, then drops. The floor of that sawtooth is 0.390um, + # comfortable for 0.12 and 10nm short of 0.4, so the violations repeat with + # the via pitch of 0.80um rather than scaling with the size. + # + # Shrinking the array region by the difference between the two rules lifts + # the whole sawtooth by that amount, floor included: 0.390 -> 0.670um. A + # column enters later than it would, so some sizes get one fewer via + # row/column -- slightly more series resistance into the plate, which a MIM + # can afford, in exchange for an enclosure that holds at every size. + via_margin = pdk.get_grule("capmet").get("via_enclosure") + if via_margin is not None: + # the via that contacts the plate from below: met5 -> via4, met3 -> via2 + via_glayer = "via" + str(int(capmettop[3:]) - 1) + generic = pdk.get_grule(capmettop, via_glayer)["min_enclosure"] + extra = max(0.0, float(via_margin) - float(generic)) + array_size = (size[0] - 2 * extra, size[1] - 2 * extra) + else: + array_size = size top_met_ref = mim_cap << via_array( - pdk, capmetbottom, capmettop, size=size, minus1=False, lay_bottom=False + pdk, capmetbottom, capmettop, size=array_size, minus1=False, + lay_bottom=False ) # 4. Add FuseTop layer - required for both options (defines the MIM area)