From 7fa6d832be11d997a540a74ff0dd8e2fb2e2dedc Mon Sep 17 00:00:00 2001 From: Luighi Date: Wed, 10 Sep 2025 23:30:15 -0300 Subject: [PATCH 01/11] Change mimcap option from A to B (met4-met5) --- src/glayout/pdk/gf180_mapped/gf180_grules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/glayout/pdk/gf180_mapped/gf180_grules.py b/src/glayout/pdk/gf180_mapped/gf180_grules.py index bf211155..2d8391ce 100644 --- a/src/glayout/pdk/gf180_mapped/gf180_grules.py +++ b/src/glayout/pdk/gf180_mapped/gf180_grules.py @@ -307,7 +307,7 @@ grulesobj["met4"]["met4"] = {'min_width': 0.28, 'min_separation': 0.3} grulesobj["met4"]["via4"] = {'min_enclosure': 0.12} grulesobj["met4"]["met5"] = {} -grulesobj["met4"]["capmet"] = {} +grulesobj["met4"]["capmet"] = {'min_enclosure': 0.6} grulesobj["via4"]["dnwell"] = {} grulesobj["via4"]["pwell"] = {} grulesobj["via4"]["nwell"] = {} @@ -364,5 +364,5 @@ grulesobj["capmet"]["met4"] = {} grulesobj["capmet"]["via4"] = {} grulesobj["capmet"]["met5"] = {} -grulesobj["capmet"]["capmet"] = {'capmettop': (42, 0), 'capmetbottom': (36, 0), 'min_separation': 1.2} +grulesobj["capmet"]["capmet"] = {'capmettop': (81, 0), 'capmetbottom': (46, 0), 'min_separation': 1.2} From 05bb77ec58755a1f6e4d73a5293df7c44c57db4d Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 16:50:43 +0300 Subject: [PATCH 02/11] WIP: continued implementation of mimcap --- src/glayout/pdk/gf180_mapped/gf180_mapped.py | 4 +- src/glayout/primitives/mimcap.py | 96 +++++++++++++++++--- 2 files changed, 86 insertions(+), 14 deletions(-) diff --git a/src/glayout/pdk/gf180_mapped/gf180_mapped.py b/src/glayout/pdk/gf180_mapped/gf180_mapped.py index 0b170bb9..ee4ebb86 100644 --- a/src/glayout/pdk/gf180_mapped/gf180_mapped.py +++ b/src/glayout/pdk/gf180_mapped/gf180_mapped.py @@ -33,6 +33,8 @@ # BJT layers "drc_bjt": (127, 5), "lvs_bjt": (118, 5), + "MIM_L_MK": (117, 10), + "fusetop": (75, 0), # _Label Layer Definations "metal5_label": (81,10), "metal4_label": (46,10), @@ -103,7 +105,7 @@ ], } -# note for DRC, there is mim_option 'A'. This is the one configured for use +# note for DRC, there is mim_option 'B'. This is the one configured for use gf180_lydrc_file_path = Path(__file__).resolve().parent / "gf180mcu_drc_wrapper.drc" # openfasoc_dir = Path(__file__).resolve().parent.parent.parent.parent.parent.parent.parent diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index ecf3336f..c64c0c0f 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -2,7 +2,7 @@ from glayout.pdk.mappedpdk import MappedPDK from typing import Optional from glayout.primitives.via_gen import via_array -from glayout.util.comp_utils import prec_array, to_decimal, to_float +from glayout.util.comp_utils import prec_array, to_decimal, to_float, evaluate_bbox from glayout.util.port_utils import rename_ports_by_orientation, add_ports_perimeter, print_ports from pydantic import validate_arguments from glayout.routing.straight_route import straight_route @@ -53,28 +53,98 @@ def __generate_mimcap_array_netlist(mimcap_netlist: Netlist, num_caps: int) -> N def mimcap( pdk: MappedPDK, size: tuple[float,float]=(5.0, 5.0) ) -> Component: - """create a mimcap + """create a MIM capacitor according to GF180MCU Option B + + MIM Option B structure (from GF180MCU documentation): + - FuseTop layer defines the top plate area + - Metal5 (top metal) forms the actual top plate + - CAP_MK defines the dielectric + - Metal4 (bottom metal) forms the bottom plate + - MIM_L_MK marks the capacitor's length dimension + + Note: Option B is for MIM between "Top metal" & "Top metal-1" (Met5 & Met4) + args: pdk=pdk to use - size=tuple(float,float) size of cap - ****Note: size is the size of the capmet layer + size=tuple(float,float) size of cap (this will be the FuseTop area) + ports: - top_met_...all edges, this is the metal over the capmet - bottom_met_...all edges, this is the metal below capmet + top_met_...all edges, this is the metal5 (top plate) + bottom_met_...all edges, this is the metal4 (bottom plate) """ size = pdk.snap_to_2xgrid(size) - # error checking and + + # Minimum area check per MIMTM.8a (5*5 um2) + min_area = 25.0 # 5*5 um2 + if size[0] * size[1] < min_area: + print(f"Warning: MIM cap area {size[0]*size[1]:.2f} um2 is below minimum {min_area} um2") + + # Maximum area check per MIMTM.8b (100*100 um2) + max_area = 10000.0 # 100*100 um2 + if size[0] * size[1] > max_area: + raise ValueError(f"MIM cap area {size[0]*size[1]:.2f} um2 exceeds maximum {max_area} um2") + + # Get layer construction info - for Option B this should be met5 (top) and met4 (bottom) capmettop, capmetbottom = __get_mimcap_layerconstruction_info(pdk) - # create top component + + # Verify we have the correct layers for Option B + assert capmettop == "met5", f"Expected met5 for top layer, got {capmettop}" + assert capmetbottom == "met4", f"Expected met4 for bottom layer, got {capmetbottom}" + + # Create main component mim_cap = Component() - mim_cap << rectangle(size=size, layer=pdk.get_glayer("capmet"), centered=True) + + # 1. Create FuseTop layer - this defines the MIM area according to MIMTM rules + fusetop_ref = mim_cap << rectangle( + size=size, + layer=pdk.layers["fusetop"], + centered=True + ) + + # 2. Create CAP_MK layer - dielectric layer + # Per MIMTM.7: Min FuseTop enclosure by CAP_MK = 0 (CAP_MK can be same size as FuseTop) + cap_mk_ref = mim_cap << rectangle( + size=size, + layer=pdk.get_glayer("capmet"), + centered=True + ) + + # 3. Create top metal plate (metal5) with via connections to metal4 top_met_ref = mim_cap << via_array( pdk, capmetbottom, capmettop, size=size, minus1=True, lay_bottom=False ) - bottom_met_enclosure = pdk.get_grule(capmetbottom,"capmet")["min_enclosure"] - mim_cap.add_padding(layers=(pdk.get_glayer(capmetbottom),),default=bottom_met_enclosure) - # flatten and create ports - mim_cap = add_ports_perimeter(mim_cap, layer=pdk.get_glayer(capmetbottom), prefix="bottom_met_") + + # 4. Create bottom metal plate (metal4) with proper enclosure + # Per MIMTM.3: Minimum MiM bottom plate overlap of Top plate = 0.6um + bottom_met_enclosure = max( + pdk.get_grule(capmetbottom, "capmet")["min_enclosure"], + 0.6 # MIMTM.3 rule + ) + mim_cap.add_padding( + layers=(pdk.get_glayer(capmetbottom),), + default=bottom_met_enclosure + ) + + # 5. Add MIM_L_MK layer - marks the capacitor length + # This should encircle the entire design with some margin + current_size = evaluate_bbox(mim_cap) + mim_l_mk_size = ( + current_size[0] + 0.2, # Add 0.1um margin on each side + current_size[1] + 0.2 + ) + + mim_l_mk_ref = mim_cap << rectangle( + size=mim_l_mk_size, + layer=pdk.layers["MIM_L_MK"], + centered=True + ) + + # Create ports + mim_cap = add_ports_perimeter( + mim_cap, + layer=pdk.get_glayer(capmetbottom), + prefix="bottom_met_" + ) mim_cap.add_ports(top_met_ref.get_ports_list()) component = rename_ports_by_orientation(mim_cap).flatten() From f466d1e5318e9c64a6e8a5bf208734852f2e9352 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 17:09:27 +0300 Subject: [PATCH 03/11] WIP: implemented optionA and optionB. Need to test --- src/glayout/primitives/mimcap.py | 118 +++++++++++++++++++------------ 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index c64c0c0f..3b85500b 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -51,29 +51,41 @@ def __generate_mimcap_array_netlist(mimcap_netlist: Netlist, num_caps: int) -> N #@cell def mimcap( - pdk: MappedPDK, size: tuple[float,float]=(5.0, 5.0) + pdk: MappedPDK, size: tuple[float,float]=(5.0, 5.0), option: str = "B" ) -> Component: - """create a MIM capacitor according to GF180MCU Option B + """create a MIM capacitor according to GF180MCU Option A or Option B - MIM Option B structure (from GF180MCU documentation): + MIM Option A structure (Metal3-Metal2 stack): + - FuseTop layer defines the top plate of MIM capacitor + - Metal3 forms the actual top plate + - CAP_MK defines the dielectric layer + - Metal2 forms the bottom plate + - For 3 metal layer processes + + MIM Option B structure (Metal5-Metal4 stack): - FuseTop layer defines the top plate area - Metal5 (top metal) forms the actual top plate - CAP_MK defines the dielectric - Metal4 (bottom metal) forms the bottom plate - MIM_L_MK marks the capacitor's length dimension - - Note: Option B is for MIM between "Top metal" & "Top metal-1" (Met5 & Met4) + - For 4+ metal layer processes args: pdk=pdk to use - size=tuple(float,float) size of cap (this will be the FuseTop area) + size=tuple(float,float) size of cap + option=str either "A" for Metal3-Metal2 or "B" for Metal5-Metal4 (default: "B") ports: - top_met_...all edges, this is the metal5 (top plate) - bottom_met_...all edges, this is the metal4 (bottom plate) + top_met_...all edges, this is the top metal plate + bottom_met_...all edges, this is the bottom metal plate """ size = pdk.snap_to_2xgrid(size) + # Validate option parameter - default to Option B if invalid + if option not in ["A", "B"]: + print(f"Warning: Invalid option '{option}'. Defaulting to Option B") + option = "B" + # Minimum area check per MIMTM.8a (5*5 um2) min_area = 25.0 # 5*5 um2 if size[0] * size[1] < min_area: @@ -84,61 +96,68 @@ def mimcap( if size[0] * size[1] > max_area: raise ValueError(f"MIM cap area {size[0]*size[1]:.2f} um2 exceeds maximum {max_area} um2") - # Get layer construction info - for Option B this should be met5 (top) and met4 (bottom) - capmettop, capmetbottom = __get_mimcap_layerconstruction_info(pdk) - - # Verify we have the correct layers for Option B - assert capmettop == "met5", f"Expected met5 for top layer, got {capmettop}" - assert capmetbottom == "met4", f"Expected met4 for bottom layer, got {capmetbottom}" + # Set layer construction based on option + if option == "A": + # Option A: Metal3-Metal2 stack (for 3 metal layer processes) + capmettop = "met3" + capmetbottom = "met2" + else: # option == "B" + # Option B: Metal5-Metal4 stack (for 4+ metal layer processes) + capmettop = "met5" + capmetbottom = "met4" # Create main component mim_cap = Component() - # 1. Create FuseTop layer - this defines the MIM area according to MIMTM rules - fusetop_ref = mim_cap << rectangle( - size=size, - layer=pdk.layers["fusetop"], - centered=True - ) - - # 2. Create CAP_MK layer - dielectric layer - # Per MIMTM.7: Min FuseTop enclosure by CAP_MK = 0 (CAP_MK can be same size as FuseTop) + # 1. Create CAP_MK layer - dielectric layer (common to both options) cap_mk_ref = mim_cap << rectangle( size=size, layer=pdk.get_glayer("capmet"), centered=True ) - # 3. Create top metal plate (metal5) with via connections to metal4 + # 2. Create top and bottom metal plates with via connections top_met_ref = mim_cap << via_array( pdk, capmetbottom, capmettop, size=size, minus1=True, lay_bottom=False ) - # 4. Create bottom metal plate (metal4) with proper enclosure - # Per MIMTM.3: Minimum MiM bottom plate overlap of Top plate = 0.6um - bottom_met_enclosure = max( - pdk.get_grule(capmetbottom, "capmet")["min_enclosure"], - 0.6 # MIMTM.3 rule - ) + # 3. Create bottom metal plate with proper enclosure + # Per design rules: Minimum bottom plate overlap of top plate = 0.6um + try: + bottom_met_enclosure = pdk.get_grule(capmetbottom, "capmet")["min_enclosure"] + except (KeyError, NotImplementedError): + bottom_met_enclosure = 0.6 # Default fallback + + bottom_met_enclosure = max(bottom_met_enclosure, 0.6) # Ensure minimum 0.6um + mim_cap.add_padding( layers=(pdk.get_glayer(capmetbottom),), default=bottom_met_enclosure ) - # 5. Add MIM_L_MK layer - marks the capacitor length - # This should encircle the entire design with some margin - current_size = evaluate_bbox(mim_cap) - mim_l_mk_size = ( - current_size[0] + 0.2, # Add 0.1um margin on each side - current_size[1] + 0.2 - ) - - mim_l_mk_ref = mim_cap << rectangle( - size=mim_l_mk_size, - layer=pdk.layers["MIM_L_MK"], + # 4. Add FuseTop layer - required for both options (defines the MIM area) + fusetop_ref = mim_cap << rectangle( + size=size, + layer=pdk.layers["fusetop"], centered=True ) + # 5. Add option-specific layers + if option == "B": + # Option B specific: Add MIM_L_MK layer (marks the capacitor length) + current_size = evaluate_bbox(mim_cap) + mim_l_mk_size = ( + current_size[0] + 0.2, # Add 0.1um margin on each side + current_size[1] + 0.2 + ) + + mim_l_mk_ref = mim_cap << rectangle( + size=mim_l_mk_size, + layer=pdk.layers["MIM_L_MK"], + centered=True + ) + # Option A: Only needs FuseTop, CAP_MK, and metal layers (no MIM_L_MK) + # Create ports mim_cap = add_ports_perimeter( mim_cap, @@ -155,20 +174,31 @@ def mimcap( return component #@cell -def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,float] = (5.0,5.0), rmult: Optional[int]=1) -> Component: +def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,float] = (5.0,5.0), rmult: Optional[int]=1, option: str = "B") -> Component: """create mimcap array args: pdk to use + rows = number of rows + columns = number of columns size = tuple(float,float) size of a single cap + rmult = routing multiplier + option = "A" for Metal3-Metal2 or "B" for Metal5-Metal4 (default: "B") ****Note: size is the size of the capmet layer ports: cap_x_y_top_met_...all edges, this is the metal over the capmet in row x, col y cap_x_y_bottom_met_...all edges, this is the metal below capmet in row x, col y """ - capmettop, capmetbottom = __get_mimcap_layerconstruction_info(pdk) + # Set layer construction based on option + if option == "A": + capmettop = "met3" + capmetbottom = "met2" + else: # option == "B" + capmettop = "met5" + capmetbottom = "met4" + mimcap_arr = Component() # create the mimcap array - mimcap_single = mimcap(pdk, size) + mimcap_single = mimcap(pdk, size, option=option) mimcap_space = pdk.get_grule("capmet")["min_separation"] #+ evaluate_bbox(mimcap_single)[0] array_ref = mimcap_arr << prec_array(mimcap_single, rows, columns, spacing=2*[mimcap_space]) mimcap_arr.add_ports(array_ref.get_ports_list()) From 771682a140d4a29ae1f4c9bb0d327dd923cc0e40 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 17:35:25 +0300 Subject: [PATCH 04/11] WIP: resized some of the layers. Need to check that this works Previous commit, optionB capcitor gets extracted as cap_mim_2f0_m4m5_noshield --- src/glayout/primitives/mimcap.py | 56 ++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index 3b85500b..6fda92be 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -2,7 +2,7 @@ from glayout.pdk.mappedpdk import MappedPDK from typing import Optional from glayout.primitives.via_gen import via_array -from glayout.util.comp_utils import prec_array, to_decimal, to_float, evaluate_bbox +from glayout.util.comp_utils import prec_array, to_decimal, to_float, evaluate_bbox, align_comp_to_port from glayout.util.port_utils import rename_ports_by_orientation, add_ports_perimeter, print_ports from pydantic import validate_arguments from glayout.routing.straight_route import straight_route @@ -109,19 +109,7 @@ def mimcap( # Create main component mim_cap = Component() - # 1. Create CAP_MK layer - dielectric layer (common to both options) - cap_mk_ref = mim_cap << rectangle( - size=size, - layer=pdk.get_glayer("capmet"), - centered=True - ) - - # 2. Create top and bottom metal plates with via connections - top_met_ref = mim_cap << via_array( - pdk, capmetbottom, capmettop, size=size, minus1=True, lay_bottom=False - ) - - # 3. Create bottom metal plate with proper enclosure + # 1. Create bottom metal plate with proper enclosure first # Per design rules: Minimum bottom plate overlap of top plate = 0.6um try: bottom_met_enclosure = pdk.get_grule(capmetbottom, "capmet")["min_enclosure"] @@ -129,26 +117,48 @@ def mimcap( bottom_met_enclosure = 0.6 # Default fallback bottom_met_enclosure = max(bottom_met_enclosure, 0.6) # Ensure minimum 0.6um + bottom_plate_size = (size[0] + 2*bottom_met_enclosure, size[1] + 2*bottom_met_enclosure) - mim_cap.add_padding( - layers=(pdk.get_glayer(capmetbottom),), - default=bottom_met_enclosure + bottom_met_ref = mim_cap << rectangle( + size=bottom_plate_size, + layer=pdk.get_glayer(capmetbottom), + centered=True + ) + + # 2. Create CAP_MK layer - dielectric layer (with 0.6um overhang to match KLayout generator) + cap_mk_overhang = 0.6 # 0.6um overhang on each side to match KLayout standard + cap_mk_size = (size[0] + 2*cap_mk_overhang, size[1] + 2*cap_mk_overhang) + cap_mk_ref = mim_cap << rectangle( + size=cap_mk_size, + layer=pdk.get_glayer("capmet"), + centered=True + ) + + # 3. Create top metal plate with via connections + top_met_ref = mim_cap << via_array( + pdk, capmetbottom, capmettop, size=size, minus1=True, lay_bottom=False ) # 4. Add FuseTop layer - required for both options (defines the MIM area) - fusetop_ref = mim_cap << rectangle( + fusetop_comp = rectangle( size=size, layer=pdk.layers["fusetop"], centered=True ) + # Add ports to FuseTop for alignment purposes + fusetop_comp = add_ports_perimeter(fusetop_comp, layer=pdk.layers["fusetop"], prefix="fusetop_") + + # Add the FuseTop component to the main component + fusetop_ref = mim_cap << fusetop_comp + # 5. Add option-specific layers if option == "B": # Option B specific: Add MIM_L_MK layer (marks the capacitor length) - current_size = evaluate_bbox(mim_cap) + # MIM_L_MK should have height of 0.1um and denote the length of the capacitor mim_l_mk_size = ( - current_size[0] + 0.2, # Add 0.1um margin on each side - current_size[1] + 0.2 + size[0], # x = length of capacitor (same as FuseTop) + 0.1 # y = 0.1um height as specified ) mim_l_mk_ref = mim_cap << rectangle( @@ -156,6 +166,10 @@ def mimcap( layer=pdk.layers["MIM_L_MK"], centered=True ) + + # Align MIM_L_MK to the south border of the MIM capacitor + # MIM_L_MK center aligned horizontally, top edge aligned to MIM cap south edge + align_comp_to_port(mim_l_mk_ref, fusetop_ref.ports["fusetop_S"], alignment=('c', 't')) # Option A: Only needs FuseTop, CAP_MK, and metal layers (no MIM_L_MK) # Create ports From 8c56e225fdb2e968bf8c46a5ce49fbf66280a4f0 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 18:21:53 +0300 Subject: [PATCH 05/11] EDIT: changed the size of the via-array --- src/glayout/primitives/mimcap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index 6fda92be..e06c9554 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -135,8 +135,9 @@ def mimcap( ) # 3. Create top metal plate with via connections + # Use minus1=False to get maximum via coverage like KLayout generator top_met_ref = mim_cap << via_array( - pdk, capmetbottom, capmettop, size=size, minus1=True, lay_bottom=False + pdk, capmetbottom, capmettop, size=size, minus1=False, lay_bottom=False ) # 4. Add FuseTop layer - required for both options (defines the MIM area) From 18f26d7754198e986ac777d8d2f94ad969f87d3e Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Sat, 27 Sep 2025 14:37:54 +0300 Subject: [PATCH 06/11] EDIT: try to add vias at the bottom of the mimcap, from m4 to m5 --- src/glayout/primitives/mimcap.py | 106 +++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 11 deletions(-) diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index e06c9554..5e051dc5 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -51,7 +51,8 @@ def __generate_mimcap_array_netlist(mimcap_netlist: Netlist, num_caps: int) -> N #@cell def mimcap( - pdk: MappedPDK, size: tuple[float,float]=(5.0, 5.0), option: str = "B" + pdk: MappedPDK, size: tuple[float,float]=(5.0, 5.0), option: str = "B", + extension_width: float = 0.0, extension_length: float = 0.0 ) -> Component: """create a MIM capacitor according to GF180MCU Option A or Option B @@ -74,10 +75,12 @@ def mimcap( pdk=pdk to use size=tuple(float,float) size of cap option=str either "A" for Metal3-Metal2 or "B" for Metal5-Metal4 (default: "B") + extension_width=float width of bottom plate extensions for via connections (default: 0.0um) + extension_length=float length of bottom plate extensions outside capacitor (default: 0.0um) ports: top_met_...all edges, this is the top metal plate - bottom_met_...all edges, this is the bottom metal plate + met5_...all edges, this is the bottom metal plate connection through metal5 vias """ size = pdk.snap_to_2xgrid(size) @@ -173,12 +176,91 @@ def mimcap( align_comp_to_port(mim_l_mk_ref, fusetop_ref.ports["fusetop_S"], alignment=('c', 't')) # Option A: Only needs FuseTop, CAP_MK, and metal layers (no MIM_L_MK) - # Create ports - mim_cap = add_ports_perimeter( - mim_cap, - layer=pdk.get_glayer(capmetbottom), - prefix="bottom_met_" + # 6. Add bottom metal extensions and via connections to metal5 + # Use the provided extension parameters + + # Calculate positions for extensions on each side + # Get bottom plate dimensions [x-length, y-length] + bottom_plate_size = evaluate_bbox(bottom_met_ref) + bottom_plate_center = bottom_met_ref.center + + # Calculate edge positions from center and dimensions + half_width = bottom_plate_size[0] / 2 + half_height = bottom_plate_size[1] / 2 + + # Create extensions on all four sides + extensions = [] + + extension_width = 2*half_width + extension_length = 0.4 # 0.4um length of the extensions + # South extension + south_ext_size = (extension_width, extension_length) + south_ext_pos = (bottom_plate_center[0], bottom_plate_center[1] - half_height - extension_length/2) + south_ext = mim_cap << rectangle( + size=south_ext_size, + layer=pdk.get_glayer(capmetbottom), + centered=True + ) + south_ext.move(south_ext_pos) + extensions.append(("S", south_ext, south_ext_size)) + + """ + # North extension + north_ext_size = (extension_width, extension_length) + north_ext_pos = (bottom_plate_center[0], bottom_plate_center[1] + half_height + extension_length/2) + north_ext = mim_cap << rectangle( + size=north_ext_size, + layer=pdk.get_glayer(capmetbottom), + centered=True + ) + north_ext.move(north_ext_pos) + extensions.append(("N", north_ext, north_ext_size)) + + # East extension + east_ext_size = (extension_length, extension_width) + east_ext_pos = (bottom_plate_center[0] + half_width + extension_length/2, bottom_plate_center[1]) + east_ext = mim_cap << rectangle( + size=east_ext_size, + layer=pdk.get_glayer(capmetbottom), + centered=True + ) + east_ext.move(east_ext_pos) + extensions.append(("E", east_ext, east_ext_size)) + + # West extension + west_ext_size = (extension_length, extension_width) + west_ext_pos = (bottom_plate_center[0] - half_width - extension_length/2, bottom_plate_center[1]) + west_ext = mim_cap << rectangle( + size=west_ext_size, + layer=pdk.get_glayer(capmetbottom), + centered=True ) + west_ext.move(west_ext_pos) + extensions.append(("W", west_ext, west_ext_size)) + """ + + # 7. Add via arrays from extensions to metal5 + via_refs = [] + for direction, ext_ref, ext_size in extensions: + # Create via array from bottom metal to metal5 + via_ref = mim_cap << via_array( + pdk, + capmetbottom, + "met5", + size=ext_size, + # minus1=True, # Use minus1=True for smaller extensions + lay_bottom=False + ) + # Align via array to the extension + via_ref.move(ext_ref.center) + via_refs.append((direction, via_ref)) + + # 8. Create ports on metal5 instead of bottom metal + # Add ports to the via arrays (which have metal5 on top) + for direction, via_ref in via_refs: + mim_cap.add_ports(via_ref.get_ports_list()) + + # Add top metal ports (unchanged) mim_cap.add_ports(top_met_ref.get_ports_list()) component = rename_ports_by_orientation(mim_cap).flatten() @@ -189,7 +271,7 @@ def mimcap( return component #@cell -def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,float] = (5.0,5.0), rmult: Optional[int]=1, option: str = "B") -> Component: +def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,float] = (5.0,5.0), rmult: Optional[int]=1, option: str = "B", extension_width: float = 0.0, extension_length: float = 0.0) -> Component: """create mimcap array args: pdk to use @@ -198,10 +280,12 @@ def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,floa size = tuple(float,float) size of a single cap rmult = routing multiplier option = "A" for Metal3-Metal2 or "B" for Metal5-Metal4 (default: "B") + extension_width = width of bottom plate extensions for via connections (default: 2.0um) + extension_length = length of bottom plate extensions outside capacitor (default: 1.0um) ****Note: size is the size of the capmet layer ports: cap_x_y_top_met_...all edges, this is the metal over the capmet in row x, col y - cap_x_y_bottom_met_...all edges, this is the metal below capmet in row x, col y + cap_x_y_met5_...all edges, this is the bottom metal connection through metal5 vias in row x, col y """ # Set layer construction based on option if option == "A": @@ -213,7 +297,7 @@ def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,floa mimcap_arr = Component() # create the mimcap array - mimcap_single = mimcap(pdk, size, option=option) + mimcap_single = mimcap(pdk, size, option=option, extension_width=extension_width, extension_length=extension_length) mimcap_space = pdk.get_grule("capmet")["min_separation"] #+ evaluate_bbox(mimcap_single)[0] array_ref = mimcap_arr << prec_array(mimcap_single, rows, columns, spacing=2*[mimcap_space]) mimcap_arr.add_ports(array_ref.get_ports_list()) @@ -224,7 +308,7 @@ def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,floa bl_mimcap = f"row{rownum}_col{colnum}_" right_mimcap = f"row{rownum}_col{colnum+1}_" top_mimcap = f"row{rownum+1}_col{colnum}_" - for level,layer in [("bottom_met_",capmetbottom),("top_met_",capmettop)]: + for level,layer in [("met5_","met5"),("top_met_",capmettop)]: bl_east_port = mimcap_arr.ports.get(bl_mimcap+level+"E") r_west_port = mimcap_arr.ports.get(right_mimcap+level+"W") bl_north_port = mimcap_arr.ports.get(bl_mimcap+level+"N") From 06fe9cbf95963e9193a3aaaad50fccd4e2bc5a9b Mon Sep 17 00:00:00 2001 From: Luighi Date: Sat, 15 Aug 2026 02:08:18 -0300 Subject: [PATCH 07/11] Fix issue with duplicated port and size --- src/glayout/primitives/mimcap.py | 104 +++++++++++-------------------- 1 file changed, 36 insertions(+), 68 deletions(-) diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index 5e051dc5..26553fc3 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -1,7 +1,7 @@ from glayout.backend import Component, cell, rectangle from glayout.pdk.mappedpdk import MappedPDK from typing import Optional -from glayout.primitives.via_gen import via_array +from glayout.primitives.via_gen import via_array, via_stack from glayout.util.comp_utils import prec_array, to_decimal, to_float, evaluate_bbox, align_comp_to_port from glayout.util.port_utils import rename_ports_by_orientation, add_ports_perimeter, print_ports from pydantic import validate_arguments @@ -144,37 +144,38 @@ def mimcap( ) # 4. Add FuseTop layer - required for both options (defines the MIM area) - fusetop_comp = rectangle( - size=size, - layer=pdk.layers["fusetop"], - centered=True - ) - - # Add ports to FuseTop for alignment purposes - fusetop_comp = add_ports_perimeter(fusetop_comp, layer=pdk.layers["fusetop"], prefix="fusetop_") - - # Add the FuseTop component to the main component - fusetop_ref = mim_cap << fusetop_comp - - # 5. Add option-specific layers - if option == "B": - # Option B specific: Add MIM_L_MK layer (marks the capacitor length) - # MIM_L_MK should have height of 0.1um and denote the length of the capacitor - mim_l_mk_size = ( - size[0], # x = length of capacitor (same as FuseTop) - 0.1 # y = 0.1um height as specified - ) - - mim_l_mk_ref = mim_cap << rectangle( - size=mim_l_mk_size, - layer=pdk.layers["MIM_L_MK"], + if (pdk=="gf180"): + fusetop_comp = rectangle( + size=size, + layer=pdk.layers["fusetop"], centered=True ) + + # Add ports to FuseTop for alignment purposes + fusetop_comp = add_ports_perimeter(fusetop_comp, layer=pdk.layers["fusetop"], prefix="fusetop_") - # Align MIM_L_MK to the south border of the MIM capacitor - # MIM_L_MK center aligned horizontally, top edge aligned to MIM cap south edge - align_comp_to_port(mim_l_mk_ref, fusetop_ref.ports["fusetop_S"], alignment=('c', 't')) - # Option A: Only needs FuseTop, CAP_MK, and metal layers (no MIM_L_MK) + # Add the FuseTop component to the main component + fusetop_ref = mim_cap << fusetop_comp + + # 5. Add option-specific layers + if option == "B": + # Option B specific: Add MIM_L_MK layer (marks the capacitor length) + # MIM_L_MK should have height of 0.1um and denote the length of the capacitor + mim_l_mk_size = ( + size[0], # x = length of capacitor (same as FuseTop) + 0.1 # y = 0.1um height as specified + ) + + mim_l_mk_ref = mim_cap << rectangle( + size=mim_l_mk_size, + layer=pdk.layers["MIM_L_MK"], + centered=True + ) + + # Align MIM_L_MK to the south border of the MIM capacitor + # MIM_L_MK center aligned horizontally, top edge aligned to MIM cap south edge + align_comp_to_port(mim_l_mk_ref, fusetop_ref.ports["fusetop_S"], alignment=('c', 't')) + # Option A: Only needs FuseTop, CAP_MK, and metal layers (no MIM_L_MK) # 6. Add bottom metal extensions and via connections to metal5 # Use the provided extension parameters @@ -191,8 +192,10 @@ def mimcap( # Create extensions on all four sides extensions = [] - extension_width = 2*half_width - extension_length = 0.4 # 0.4um length of the extensions + viastack = via_stack(pdk, capmetbottom, capmettop) + viadim = evaluate_bbox(viastack)[0] + extension_width = 2*half_width + extension_length = max(0.4, float(viadim)) # 0.4um length of the extensions # South extension south_ext_size = (extension_width, extension_length) south_ext_pos = (bottom_plate_center[0], bottom_plate_center[1] - half_height - extension_length/2) @@ -204,41 +207,6 @@ def mimcap( south_ext.move(south_ext_pos) extensions.append(("S", south_ext, south_ext_size)) - """ - # North extension - north_ext_size = (extension_width, extension_length) - north_ext_pos = (bottom_plate_center[0], bottom_plate_center[1] + half_height + extension_length/2) - north_ext = mim_cap << rectangle( - size=north_ext_size, - layer=pdk.get_glayer(capmetbottom), - centered=True - ) - north_ext.move(north_ext_pos) - extensions.append(("N", north_ext, north_ext_size)) - - # East extension - east_ext_size = (extension_length, extension_width) - east_ext_pos = (bottom_plate_center[0] + half_width + extension_length/2, bottom_plate_center[1]) - east_ext = mim_cap << rectangle( - size=east_ext_size, - layer=pdk.get_glayer(capmetbottom), - centered=True - ) - east_ext.move(east_ext_pos) - extensions.append(("E", east_ext, east_ext_size)) - - # West extension - west_ext_size = (extension_length, extension_width) - west_ext_pos = (bottom_plate_center[0] - half_width - extension_length/2, bottom_plate_center[1]) - west_ext = mim_cap << rectangle( - size=west_ext_size, - layer=pdk.get_glayer(capmetbottom), - centered=True - ) - west_ext.move(west_ext_pos) - extensions.append(("W", west_ext, west_ext_size)) - """ - # 7. Add via arrays from extensions to metal5 via_refs = [] for direction, ext_ref, ext_size in extensions: @@ -257,8 +225,8 @@ def mimcap( # 8. Create ports on metal5 instead of bottom metal # Add ports to the via arrays (which have metal5 on top) - for direction, via_ref in via_refs: - mim_cap.add_ports(via_ref.get_ports_list()) + #for direction, via_ref in via_refs: + # mim_cap.add_ports(via_ref.get_ports_list()) # Add top metal ports (unchanged) mim_cap.add_ports(top_met_ref.get_ports_list()) From d97f0f1b0e9c35ba42a8fad2d8d816aa3c81f998 Mon Sep 17 00:00:00 2001 From: Luighi Date: Sat, 15 Aug 2026 03:00:52 -0300 Subject: [PATCH 08/11] Delete fixed met5 routes --- src/glayout/primitives/mimcap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index 26553fc3..e3154be6 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -276,7 +276,7 @@ def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,floa bl_mimcap = f"row{rownum}_col{colnum}_" right_mimcap = f"row{rownum}_col{colnum+1}_" top_mimcap = f"row{rownum+1}_col{colnum}_" - for level,layer in [("met5_","met5"),("top_met_",capmettop)]: + for level,layer in [("top_met_",capmettop)]: bl_east_port = mimcap_arr.ports.get(bl_mimcap+level+"E") r_west_port = mimcap_arr.ports.get(right_mimcap+level+"W") bl_north_port = mimcap_arr.ports.get(bl_mimcap+level+"N") From 455a86708be4d6438e1e14ad6d1cf95919064011 Mon Sep 17 00:00:00 2001 From: Luighi Date: Sat, 15 Aug 2026 03:01:48 -0300 Subject: [PATCH 09/11] Avoid using bottom ports in mimcap replaced by top ports --- src/glayout/cells/composite/opamp/opamp.py | 2 +- src/glayout/cells/composite/opamp/opamp_twostage.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/glayout/cells/composite/opamp/opamp.py b/src/glayout/cells/composite/opamp/opamp.py index cf9d1c0f..d8ade1a4 100644 --- a/src/glayout/cells/composite/opamp/opamp.py +++ b/src/glayout/cells/composite/opamp/opamp.py @@ -223,7 +223,7 @@ def __add_output_stage( # Locate output stage relative position # x-coordinate: Center of SW capacitor in array # y-coordinate: Top of NMOS blocks - xref_port = opamp_top.ports["mimcap_row0_col0_bottom_met_S"] + xref_port = opamp_top.ports["mimcap_row0_col0_top_met_S"] x_cord = xref_port.center[0] - xref_port.width/2 y_cord = opamp_top.ports["commonsource_cmirror_output_R_tie_N_top_met_N"].center[1] dims = evaluate_bbox(amp_fet_ref) diff --git a/src/glayout/cells/composite/opamp/opamp_twostage.py b/src/glayout/cells/composite/opamp/opamp_twostage.py index 5ac43f91..b7b75405 100644 --- a/src/glayout/cells/composite/opamp/opamp_twostage.py +++ b/src/glayout/cells/composite/opamp/opamp_twostage.py @@ -133,7 +133,8 @@ def __add_mimcap_arr(pdk: MappedPDK, opamp_top: Component, mim_cap_size, mim_cap intermediate_output = set_port_orientation(n_to_p_output_route.ports["con_S"],"N") # opamp_top << L_route(pdk, mimcaps_ref.ports["row0_col0_top_met_N"], intermediate_output, hwidth=1, hglayer="met4", vglayer="met4") # C route up right up to reach the mimcap port, extension is - opamp_top << L_route(pdk, intermediate_output, mimcaps_ref.ports["row0_col0_bottom_met_E"], fullbottom=True, vglayer="met5", hglayer="met4") + opamp_top << L_route(pdk, intermediate_output, + mimcaps_ref.ports["row0_col0_top_met_E"], fullbottom=True, vglayer="met5", hglayer="met4") opamp_top.add_ports(mimcaps_ref.get_ports_list(),prefix="mimcap_") # add the cs output as a port opamp_top.add_port(name="commonsource_output_E", port=intermediate_output) From b52f12f8f1dc42135e5bee1e6038bb2882b9ccd2 Mon Sep 17 00:00:00 2001 From: Luighi Date: Sun, 16 Aug 2026 03:25:38 -0300 Subject: [PATCH 10/11] Update functions to make mimcaps recognizable --- src/glayout/primitives/mimcap.py | 151 ++++++++++++++++++++++--------- 1 file changed, 109 insertions(+), 42 deletions(-) diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index e3154be6..8c44816d 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -1,5 +1,7 @@ +from os import rename from glayout.backend import Component, cell, rectangle from glayout.pdk.mappedpdk import MappedPDK +from glayout.pdk.gf180_mapped import gf180_mapped_pdk as gf180 from typing import Optional from glayout.primitives.via_gen import via_array, via_stack from glayout.util.comp_utils import prec_array, to_decimal, to_float, evaluate_bbox, align_comp_to_port @@ -51,7 +53,8 @@ def __generate_mimcap_array_netlist(mimcap_netlist: Netlist, num_caps: int) -> N #@cell def mimcap( - pdk: MappedPDK, size: tuple[float,float]=(5.0, 5.0), option: str = "B", + pdk: MappedPDK, size: tuple[float,float]=(5.0, 5.0), option: str = "B", + with_extension: bool = True, extension_direction: str = "S", extension_width: float = 0.0, extension_length: float = 0.0 ) -> Component: """create a MIM capacitor according to GF180MCU Option A or Option B @@ -75,6 +78,8 @@ def mimcap( pdk=pdk to use size=tuple(float,float) size of cap option=str either "A" for Metal3-Metal2 or "B" for Metal5-Metal4 (default: "B") + with_extension=bool enable extension or not + extension_direction=str specify which direction to use for the extension extension_width=float width of bottom plate extensions for via connections (default: 0.0um) extension_length=float length of bottom plate extensions outside capacitor (default: 0.0um) @@ -144,7 +149,7 @@ def mimcap( ) # 4. Add FuseTop layer - required for both options (defines the MIM area) - if (pdk=="gf180"): + if (pdk is gf180): fusetop_comp = rectangle( size=size, layer=pdk.layers["fusetop"], @@ -189,44 +194,104 @@ def mimcap( half_width = bottom_plate_size[0] / 2 half_height = bottom_plate_size[1] / 2 - # Create extensions on all four sides - extensions = [] - - viastack = via_stack(pdk, capmetbottom, capmettop) - viadim = evaluate_bbox(viastack)[0] - extension_width = 2*half_width - extension_length = max(0.4, float(viadim)) # 0.4um length of the extensions - # South extension - south_ext_size = (extension_width, extension_length) - south_ext_pos = (bottom_plate_center[0], bottom_plate_center[1] - half_height - extension_length/2) - south_ext = mim_cap << rectangle( - size=south_ext_size, - layer=pdk.get_glayer(capmetbottom), - centered=True - ) - south_ext.move(south_ext_pos) - extensions.append(("S", south_ext, south_ext_size)) + # Create extensions on the selected direction + if with_extension: + extensions = [] + + viastack = via_stack(pdk, capmetbottom, capmettop) + viadim = evaluate_bbox(viastack)[0] + + if extension_direction=="S": + extension_width = 2*half_width + extension_length = max(0.4, float(viadim)) # 0.4um length of the extensions + # South extension + ext_size = (extension_width, extension_length) + ext_pos = (bottom_plate_center[0], bottom_plate_center[1] - half_height - extension_length/2) + + elif extension_direction=="N": + + extension_width = 2*half_width + extension_length = max(0.4, float(viadim)) # 0.4um length of the extensions + # North extension + ext_size = (extension_width, extension_length) + ext_pos = (bottom_plate_center[0], bottom_plate_center[1] + half_height + extension_length/2) + + elif extension_direction=="W": + + extension_length = 2*half_height + extension_width = max(0.4, float(viadim)) # 0.4um length of the extensions + # North extension + ext_size = (extension_width, extension_length) + ext_pos = (bottom_plate_center[0] - half_width - extension_width/2, bottom_plate_center[1]) + + elif extension_direction=="E": + + extension_length = 2*half_height + extension_width = max(0.4, float(viadim)) # 0.4um length of the extensions + # North extension + ext_size = (extension_width, extension_length) + ext_pos = (bottom_plate_center[0] + half_width + extension_width/2, bottom_plate_center[1]) + + else: + raise ValueError(f"extension_direction must be a valid value from" + f" [S,N,W,E]. Received {extension_direction}.") + + ext = mim_cap << rename_ports_by_orientation(rectangle( + size=ext_size, + layer=pdk.get_glayer(capmetbottom), + centered=True + )) + ext.move(ext_pos) + + extensions.append((extension_direction, ext, ext_size)) - # 7. Add via arrays from extensions to metal5 - via_refs = [] - for direction, ext_ref, ext_size in extensions: - # Create via array from bottom metal to metal5 - via_ref = mim_cap << via_array( - pdk, - capmetbottom, - "met5", - size=ext_size, - # minus1=True, # Use minus1=True for smaller extensions - lay_bottom=False - ) - # Align via array to the extension - via_ref.move(ext_ref.center) - via_refs.append((direction, via_ref)) + # 7. Add via arrays from extensions to capmettop + via_refs = [] + for direction, ext_ref, ext_size in extensions: + # Create via array from bottom metal to capmettop + via_ref = mim_cap << via_array( + pdk, + capmetbottom, + capmettop, + size=ext_size, + # minus1=True, # Use minus1=True for smaller extensions + lay_bottom=False + ) + # Align via array to the extension + via_ref.move(ext_ref.center) + via_refs.append((direction, via_ref)) - # 8. Create ports on metal5 instead of bottom metal - # Add ports to the via arrays (which have metal5 on top) - #for direction, via_ref in via_refs: - # mim_cap.add_ports(via_ref.get_ports_list()) + # 8. Create ports on top_metal instead of bottom metal + # Add ports to the via arrays (which have metal5 on top) + for direction, via_ref in via_refs: + bottom_via_prefix=f"bot_via_{direction}_" + mim_cap.add_ports(via_ref.get_ports_list(), + prefix=bottom_via_prefix) + + target_port= ext.ports.get(direction) + cord_ref=int(1 if direction in ["S", "N"] else 0) + other_cord= int(not cord_ref) + min_via_distance=0 + for via_port in via_ref.get_ports_list(): + + via_distance=abs(via_port.center[cord_ref] - + target_port.center[cord_ref]) + + if via_distance < min_via_distance: + min_via_distance = via_distance + + #Requires minimum distance according to MIM rule + if min_via_distance < 0.4: + appendix_size=[0.0,0.0] + appendix_size[cord_ref]=0.4-min_via_distance + appendix_size[other_cord]=ext_size[other_cord] + appendix=mim_cap << rectangle(size=appendix_size, + layer=pdk.get_glayer(capmetbottom), + centered=True + ) + appendix_pos=target_port.center + appendix_pos[cord_ref] = appendix_pos[cord_ref] + appendix_size[cord_ref]/2 if direction=="N" or direction=="E" else appendix_pos[cord_ref] - appendix_size[cord_ref]/2 + appendix.move(appendix_pos) # Add top metal ports (unchanged) mim_cap.add_ports(top_met_ref.get_ports_list()) @@ -265,7 +330,10 @@ def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,floa mimcap_arr = Component() # create the mimcap array - mimcap_single = mimcap(pdk, size, option=option, extension_width=extension_width, extension_length=extension_length) + mimcap_single = mimcap(pdk, size, option=option, + extension_width=extension_width, + extension_length=extension_length, + with_extension=False) mimcap_space = pdk.get_grule("capmet")["min_separation"] #+ evaluate_bbox(mimcap_single)[0] array_ref = mimcap_arr << prec_array(mimcap_single, rows, columns, spacing=2*[mimcap_space]) mimcap_arr.add_ports(array_ref.get_ports_list()) @@ -301,9 +369,8 @@ def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,floa xmin, ymin = float(arr_bbox[0][0]) - pad, float(arr_bbox[0][1]) - pad xmax, ymax = float(arr_bbox[1][0]) + pad, float(arr_bbox[1][1]) + pad plate = Component() - for level_layer in (capmettop, capmetbottom): - plate.add_polygon([(xmin,ymin),(xmax,ymin),(xmax,ymax),(xmin,ymax)], - layer=pdk.get_glayer(level_layer)) + #for level_layer in (capmettop, capmetbottom): + plate.add_polygon([(xmin,ymin),(xmax,ymin),(xmax,ymax),(xmin,ymax)], layer=pdk.get_glayer(capmetbottom)) mimcap_arr << plate # add netlist From 73a5f8366823e360d3aed1e1b7e99aac887086d6 Mon Sep 17 00:00:00 2001 From: Luighi Date: Sun, 16 Aug 2026 16:08:58 -0300 Subject: [PATCH 11/11] Workaround to pass drc in sky130 --- src/glayout/primitives/mimcap.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index 8c44816d..5d6e24d2 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -334,7 +334,14 @@ def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,floa extension_width=extension_width, extension_length=extension_length, with_extension=False) - mimcap_space = pdk.get_grule("capmet")["min_separation"] #+ evaluate_bbox(mimcap_single)[0] + try: + bottom_met_enclosure = pdk.get_grule(capmetbottom, "capmet")["min_enclosure"] + except (KeyError, NotImplementedError): + bottom_met_enclosure = 0.6 # Default fallback + + mimcap_space = max(float(pdk.get_grule(capmettop)["min_separation"]) + - 2*float(bottom_met_enclosure), + pdk.get_grule("capmet")["min_separation"]) #+ evaluate_bbox(mimcap_single)[0] array_ref = mimcap_arr << prec_array(mimcap_single, rows, columns, spacing=2*[mimcap_space]) mimcap_arr.add_ports(array_ref.get_ports_list()) # create a list of ports that should be routed to connect the array @@ -369,8 +376,9 @@ def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,floa xmin, ymin = float(arr_bbox[0][0]) - pad, float(arr_bbox[0][1]) - pad xmax, ymax = float(arr_bbox[1][0]) + pad, float(arr_bbox[1][1]) + pad plate = Component() - #for level_layer in (capmettop, capmetbottom): - plate.add_polygon([(xmin,ymin),(xmax,ymin),(xmax,ymax),(xmin,ymax)], layer=pdk.get_glayer(capmetbottom)) + for level_layer in (capmettop, capmetbottom): + plate.add_polygon([(xmin,ymin),(xmax,ymin),(xmax,ymax),(xmin,ymax)], + layer=pdk.get_glayer(level_layer)) mimcap_arr << plate # add netlist