Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/glayout/pdk/gf180_mapped/gf180_grules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"] = {}
Expand Down Expand Up @@ -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}

4 changes: 3 additions & 1 deletion src/glayout/pdk/gf180_mapped/gf180_mapped.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down
239 changes: 219 additions & 20 deletions src/glayout/primitives/mimcap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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
Expand Down Expand Up @@ -53,30 +53,216 @@ 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",
extension_width: float = 0.0, extension_length: float = 0.0
) -> Component:
"""create a mimcap
"""create a MIM capacitor according to GF180MCU Option A or Option B

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
- For 4+ metal layer processes

args:
pdk=pdk to use
size=tuple(float,float) size of cap
****Note: size is the size of the capmet layer
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 metal over the capmet
bottom_met_...all edges, this is the metal below capmet
top_met_...all edges, this is the top metal plate
met5_...all edges, this is the bottom metal plate connection through metal5 vias
"""
size = pdk.snap_to_2xgrid(size)
# error checking and
capmettop, capmetbottom = __get_mimcap_layerconstruction_info(pdk)
# create top component

# 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:
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")

# 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()
mim_cap << rectangle(size=size, layer=pdk.get_glayer("capmet"), centered=True)

# 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"]
except (KeyError, NotImplementedError):
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)

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
# 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)
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"],
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

# 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
)
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_")
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()
Expand All @@ -87,20 +273,33 @@ 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", extension_width: float = 0.0, extension_length: float = 0.0) -> 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")
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
"""
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, 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())
Expand All @@ -111,7 +310,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")
Expand Down