From 406aa36188d2486c07e852f92e4bd4a3e13c3554 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Sat, 23 Aug 2025 15:33:07 +0300 Subject: [PATCH 01/11] updated fet.py to accommodate for finger placement. the finger width check could be more sophisitcated. Currently W needs to be a multiple of fingers --- src/glayout/primitives/fet.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/glayout/primitives/fet.py b/src/glayout/primitives/fet.py index 979a3072..3bf8905d 100644 --- a/src/glayout/primitives/fet.py +++ b/src/glayout/primitives/fet.py @@ -23,6 +23,8 @@ def __gen_fingers_macro(pdk: MappedPDK, rmult: int, fingers: int, length: float, length = pdk.snap_to_2xgrid(length) width = pdk.snap_to_2xgrid(width) poly_height = pdk.snap_to_2xgrid(poly_height) + # Calculate finger width as width/fingers + finger_width = pdk.snap_to_2xgrid(width / fingers) sizing_ref_viastack = via_stack(pdk, "active_diff", "met1") # figure out poly (gate) spacing: s/d metal doesnt overlap transistor, s/d min seperation criteria is met sd_viaxdim = rmult*evaluate_bbox(via_stack(pdk, "active_diff", "met1"))[0] @@ -33,8 +35,8 @@ def __gen_fingers_macro(pdk: MappedPDK, rmult: int, fingers: int, length: float, # create a single finger finger = Component("finger") gate = finger << rectangle(size=(length, poly_height), layer=pdk.get_glayer("poly"), centered=True) - sd_viaarr = via_array(pdk, "active_diff", "met1", size=(sd_viaxdim, width), minus1=True, lay_bottom=False).copy() - interfinger_correction = via_array(pdk,"met1",inter_finger_topmet, size=(None, width),lay_every_layer=True, num_vias=(1,None)) + sd_viaarr = via_array(pdk, "active_diff", "met1", size=(sd_viaxdim, finger_width), minus1=True, lay_bottom=False).copy() + interfinger_correction = via_array(pdk,"met1",inter_finger_topmet, size=(None, finger_width),lay_every_layer=True, num_vias=(1,None)) sd_viaarr << interfinger_correction sd_viaarr_ref = finger << sd_viaarr sd_viaarr_ref.movex((poly_spacing+length) / 2) @@ -53,7 +55,7 @@ def __gen_fingers_macro(pdk: MappedPDK, rmult: int, fingers: int, length: float, # create diffusion and +doped region multiplier = rename_ports_by_orientation(centered_farray) diff_extra_enc = 2 * pdk.get_grule("mcon", "active_diff")["min_enclosure"] - diff_dims =(diff_extra_enc + evaluate_bbox(multiplier)[0], width) + diff_dims =(diff_extra_enc + evaluate_bbox(multiplier)[0], finger_width) diff = multiplier << rectangle(size=diff_dims,layer=pdk.get_glayer("active_diff"),centered=True) sd_diff_ovhg = pdk.get_grule(sdlayer, "active_diff")["min_enclosure"] sdlayer_dims = [dim + 2*sd_diff_ovhg for dim in diff_dims] @@ -85,12 +87,12 @@ def fet_netlist( length = pdk.get_grule('poly')['min_width'] ltop = length - wtop = width + wtop = width/fingers mtop = fingers * multipliers dmtop = multipliers - source_netlist=""".subckt {circuit_name} {nodes} """+f'l={ltop} w={wtop} m={mtop} dm={dmtop} '+""" -XMAIN D G S B {model} l={{l}} w={{w}} m={{m}}""" + source_netlist=""".subckt {circuit_name} {nodes} """+f'l={ltop} w={wtop} nf={fingers} m={mtop} dm={dmtop} '+""" +XMAIN D G S B {model} l={{l}} w={{w}} nf={{fingers}} m={{m}}""" for i in range(num_dummies): source_netlist += "\nXDUMMY" + str(i+1) + " B B B B {model} l={{l}} w={{w}} m={{dm}}" @@ -101,11 +103,12 @@ def fet_netlist( circuit_name=circuit_name, nodes=['D', 'G', 'S', 'B'], source_netlist=source_netlist, - instance_format="X{name} {nodes} {circuit_name} l={length} w={width} m={mult} dm={dummy_mult}", + instance_format="X{name} {nodes} {circuit_name} l={length} w={width} nf={fingers} m={mult} dm={dummy_mult}", parameters={ 'model': model, 'length': ltop, 'width': wtop, + 'fingers': fingers, 'mult': mtop / 2, 'dummy_mult': dmtop } @@ -138,7 +141,7 @@ def multiplier( sdlayer = either p+s/d for pmos or n+s/d for nmos width = expands the transistor in the y direction length = transitor length (if left None defaults to min length) - fingers = introduces additional fingers (sharing s/d) of width=width + fingers = introduces additional fingers (sharing s/d) of width=width/fingers routing = true or false, specfies if sd should be connected inter_finger_topmet = top metal of the via array laid on the source/drain regions ****NOTE: routing metal is layed over the source drain regions regardless of routing option @@ -176,6 +179,8 @@ def multiplier( raise ValueError("routing multipliers must be positive int") if fingers < 1: raise ValueError("number of fingers must be positive int") + if width % fingers != 0: + raise ValueError("width must be a multiple of fingers") # argument parsing and rule setup min_length = pdk.get_grule("poly")["min_width"] length = min_length if (length or min_length) <= min_length else length @@ -183,7 +188,8 @@ def multiplier( min_width = max(min_length, pdk.get_grule("active_diff")["min_width"]) width = min_width if (width or min_width) <= min_width else width width = pdk.snap_to_2xgrid(width) - poly_height = width + 2 * pdk.get_grule("poly", "active_diff")["overhang"] + finger_width = pdk.snap_to_2xgrid(width / fingers) + poly_height = finger_width + 2 * pdk.get_grule("poly", "active_diff")["overhang"] # call finger array multiplier = __gen_fingers_macro(pdk, interfinger_rmult, fingers, length, width, poly_height, sdlayer, inter_finger_topmet) # route all drains/ gates/ sources @@ -195,7 +201,7 @@ def multiplier( sdroute_minsep = pdk.get_grule(sd_route_topmet)["min_separation"] sdvia_ports = list() for finger in range(fingers+1): - diff_top_port = movey(sd_N_port,destination=width/2) + diff_top_port = movey(sd_N_port,destination=finger_width/2) # place sdvia such that metal does not overlap diffusion big_extension = sdroute_minsep + sdroute_minsep + sdmet_hieght/2 + sdmet_hieght sdvia_extension = big_extension if finger % 2 else sdroute_minsep + (sdmet_hieght)/2 @@ -235,7 +241,8 @@ def multiplier( else: dummyl, dummyr = dummy if dummyl or dummyr: - dummy = __gen_fingers_macro(pdk,rmult=interfinger_rmult,fingers=1,length=length,width=width,poly_height=poly_height,sdlayer=sdlayer,inter_finger_topmet="met1") + finger_width = width/fingers + dummy = __gen_fingers_macro(pdk,rmult=interfinger_rmult,fingers=1,length=length,width=finger_width,poly_height=poly_height,sdlayer=sdlayer,inter_finger_topmet="met1") dummyvia = dummy << via_stack(pdk,"poly","met1",fullbottom=True) align_comp_to_port(dummyvia,dummy.ports["row0_col0_gate_S"],layer=pdk.get_glayer("poly")) dummy << L_route(pdk,dummyvia.ports["top_met_W"],dummy.ports["leftsd_top_met_S"]) From fc393a2a8e5141570da6770f7e7d97abb182caf1 Mon Sep 17 00:00:00 2001 From: Luighi Date: Wed, 10 Sep 2025 23:30:15 -0300 Subject: [PATCH 02/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 c347e6ca5ded14e5fc0cae81cc88d28de4257ef4 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 16:50:43 +0300 Subject: [PATCH 03/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 6b79a74d..277f427b 100644 --- a/src/glayout/pdk/gf180_mapped/gf180_mapped.py +++ b/src/glayout/pdk/gf180_mapped/gf180_mapped.py @@ -30,6 +30,8 @@ "lvpwell": (204, 0), "dnwell": (12, 0), "CAP_MK": (117, 5), + "MIM_L_MK": (117, 10), + "fusetop": (75, 0), # _Label Layer Definations "metal5_label": (81,10), "metal4_label": (46,10), @@ -78,7 +80,7 @@ "active_diff_label": "comp_label", } -# 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.lydrc" # 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 fa8f622b..06000d12 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -4,7 +4,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 @@ -55,28 +55,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 79a9e9b59358833c2f0f61830247207e971144f0 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 17:09:27 +0300 Subject: [PATCH 04/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 06000d12..2dc03286 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -53,29 +53,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: @@ -86,61 +98,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, @@ -157,20 +176,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 15fc3bb52dd2ba4ba627564da17e8b4ae4ba89b6 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 17:35:25 +0300 Subject: [PATCH 05/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 2dc03286..4984924f 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -4,7 +4,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 @@ -111,19 +111,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"] @@ -131,26 +119,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( @@ -158,6 +168,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 ec4d90fc0448ca27014c44f8bfeff2058795eed8 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 18:21:53 +0300 Subject: [PATCH 06/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 4984924f..17a40c51 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -137,8 +137,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 7aa821d69ddd02fabf208f3d9c164f5358d330fa Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 16:50:43 +0300 Subject: [PATCH 07/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 6b79a74d..277f427b 100644 --- a/src/glayout/pdk/gf180_mapped/gf180_mapped.py +++ b/src/glayout/pdk/gf180_mapped/gf180_mapped.py @@ -30,6 +30,8 @@ "lvpwell": (204, 0), "dnwell": (12, 0), "CAP_MK": (117, 5), + "MIM_L_MK": (117, 10), + "fusetop": (75, 0), # _Label Layer Definations "metal5_label": (81,10), "metal4_label": (46,10), @@ -78,7 +80,7 @@ "active_diff_label": "comp_label", } -# 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.lydrc" # 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 fa8f622b..06000d12 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -4,7 +4,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 @@ -55,28 +55,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 9862ed677047c2b52a85a26fd8c1c0258053544b Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 17:09:27 +0300 Subject: [PATCH 08/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 06000d12..2dc03286 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -53,29 +53,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: @@ -86,61 +98,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, @@ -157,20 +176,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 544b5b6dd81c4e415d195fa930493fc6f91d29c0 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 17:35:25 +0300 Subject: [PATCH 09/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 2dc03286..4984924f 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -4,7 +4,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 @@ -111,19 +111,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"] @@ -131,26 +119,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( @@ -158,6 +168,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 2b528420157fcddfd258d889a1c203d21804b1c0 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Thu, 18 Sep 2025 18:21:53 +0300 Subject: [PATCH 10/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 4984924f..17a40c51 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -137,8 +137,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 0da584b9aaf85399ac5ce381e68938c2eb62f914 Mon Sep 17 00:00:00 2001 From: Vasil Yordanov Date: Sat, 27 Sep 2025 15:45:43 +0300 Subject: [PATCH 11/11] ADD: via to topmet and extension to south of the mimcap --- src/glayout/primitives/mimcap.py | 53 +++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/glayout/primitives/mimcap.py b/src/glayout/primitives/mimcap.py index 17a40c51..1f75a5ad 100644 --- a/src/glayout/primitives/mimcap.py +++ b/src/glayout/primitives/mimcap.py @@ -155,7 +155,58 @@ def mimcap( # Add the FuseTop component to the main component fusetop_ref = mim_cap << fusetop_comp - # 5. Add option-specific layers + # 5. Add south extension to bottom metal plate with via array and top metal coverage + # Extension has height 0.4u and same width as bottom plate + extension_height = 0.5 + extension_width = bottom_plate_size[0] # Same width as bottom plate + extension_size = (extension_width, extension_height) + + # Create south extension rectangle on bottom metal layer + south_extension_bottom_met_ref = mim_cap << rectangle( + size=extension_size, + layer=pdk.get_glayer(capmetbottom), + centered=True + ) + # Create south extension rectangle on top metal layer + # First create the component with ports, then add it to the main component + south_extension_top_met_comp = rectangle( + size=extension_size, + layer=pdk.get_glayer(capmettop), + centered=True + ) + + # Add perimeter ports to the top metal extension component + south_extension_top_met_comp = add_ports_perimeter( + south_extension_top_met_comp, + layer=pdk.get_glayer(capmettop), + prefix="ext_top_met_" + ) + + # Now add the component with ports to the main component + south_extension_top_met_ref = mim_cap << south_extension_top_met_comp + + # Position the extension at the south edge of the bottom plate + # The extension should be centered horizontally and positioned south of the bottom plate + extension_y_offset = -(bottom_plate_size[1]/2 + extension_height/2) + south_extension_bottom_met_ref.move((0, extension_y_offset)) + south_extension_top_met_ref.move((0, extension_y_offset)) + + # Create via array covering the south extension from bottom_met to top_met + via_array_ref = mim_cap << via_array( + pdk, capmetbottom, capmettop, + size=extension_size, + ) + + # Position the via array at the same location as the south extension + via_array_ref.move((0, extension_y_offset)) + + # Add the S port from the south edge of the top metal extension + s_port_candidates = [port for port in south_extension_top_met_ref.get_ports_list() if "ext_top_met_S" in port.name] + if s_port_candidates: + s_port = s_port_candidates[0] + mim_cap.add_port(name="S", center=s_port.center, width=s_port.width, orientation=s_port.orientation, layer=s_port.layer) + + # 6. 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