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
29 changes: 18 additions & 11 deletions src/glayout/primitives/fet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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)
Expand All @@ -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]
Expand Down Expand Up @@ -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}}"
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -176,14 +179,17 @@ 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
length = pdk.snap_to_2xgrid(length)
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
Expand All @@ -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
Expand Down Expand Up @@ -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"])
Expand Down
202 changes: 184 additions & 18 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,185 @@ 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 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")

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
bottom_met_...all edges, this is the bottom metal plate
"""
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 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
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)

# Create ports
mim_cap = add_ports_perimeter(
mim_cap,
layer=pdk.get_glayer(capmetbottom),
prefix="bottom_met_"
)
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_")
mim_cap.add_ports(top_met_ref.get_ports_list())

component = rename_ports_by_orientation(mim_cap).flatten()
Expand All @@ -87,20 +242,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())
Expand Down