"""Bondpad components for IHP PDK."""
import math
from typing import Literal
import gdsfactory as gf
from gdsfactory import Component
@gf.cell
def bondpad(
shape: Literal["octagon", "square", "circle"] = "octagon",
stack_metals: bool = True,
fill_metals: bool = False,
flip_chip: bool = False,
diameter: float = 68.0,
top_metal: str = "TopMetal2",
bottom_metal: str = "Metal1",
) -> Component:
"""Create a bondpad for wire bonding or flip-chip connection.
Args:
shape: Shape of the bondpad ("octagon", "square", or "circle").
stack_metals: Stack all metal layers from bottom to top.
fill_metals: Add metal fill patterns.
flip_chip: Enable flip-chip configuration.
diameter: Diameter or size of the bondpad in micrometers.
top_metal: Top metal layer name.
bottom_metal: Bottom metal layer name.
Returns:
Component with bondpad layout.
"""
c = Component()
# Define metal layers
layers = {
"Metal1": (8, 0),
"Metal2": (10, 0),
"Metal3": (30, 0),
"Metal4": (50, 0),
"Metal5": (67, 0),
"TopMetal1": (126, 5),
"TopMetal2": (134, 5),
}
# Define via layers
via_layers = {
"Via1": (19, 0),
"Via2": (29, 0),
"Via3": (49, 0),
"Via4": (66, 0),
"Via5": (125, 5),
}
# Passivation and other layers
passivation_open = (33, 0)
# Grid alignment
grid = 0.01
d = round(diameter / grid) * grid
# Create the main pad shape
if shape == "square":
# Square bondpad
pad = gf.components.rectangle(
size=(d, d),
layer=layers[top_metal],
centered=True,
)
c.add_ref(pad)
elif shape == "octagon":
# Octagonal bondpad
# Calculate octagon vertices
side_length = d / (1 + math.sqrt(2))
half_side = side_length / 2
vertices = [
(half_side, d / 2),
(d / 2 - half_side, d / 2),
(d / 2, d / 2 - half_side),
(d / 2, -d / 2 + half_side),
(d / 2 - half_side, -d / 2),
(-d / 2 + half_side, -d / 2),
(-d / 2, -d / 2 + half_side),
(-d / 2, d / 2 - half_side),
]
pad = gf.Component()
pad.add_polygon(vertices, layer=layers[top_metal])
c.add_ref(pad)
elif shape == "circle":
# Circular bondpad (approximated with polygon)
pad = gf.components.circle(
radius=d / 2,
layer=layers[top_metal],
)
c.add_ref(pad)
else:
raise ValueError(f"Unknown shape: {shape}")
# Stack metal layers if requested
if stack_metals:
# Create stack from bottom_metal to top_metal
metal_stack = [
"Metal1",
"Metal2",
"Metal3",
"Metal4",
"Metal5",
"TopMetal1",
"TopMetal2",
]
# Find indices for start and end
start_idx = metal_stack.index(bottom_metal)
end_idx = metal_stack.index(top_metal)
# Add metal layers
for i in range(start_idx, end_idx):
metal_name = metal_stack[i]
metal_layer = layers[metal_name]
if shape == "square":
metal = gf.components.rectangle(
size=(d * 0.95, d * 0.95), # Slightly smaller for via clearance
layer=metal_layer,
centered=True,
)
c.add_ref(metal)
elif shape == "octagon":
# Scale down octagon for lower metals
scale = 0.95
scaled_vertices = [(x * scale, y * scale) for x, y in vertices]
metal = gf.Component()
metal.add_polygon(scaled_vertices, layer=metal_layer)
c.add_ref(metal)
elif shape == "circle":
metal = gf.components.circle(
radius=d / 2 * 0.95,
layer=metal_layer,
)
c.add_ref(metal)
# Add vias between metal layers
via_mapping = {
("Metal1", "Metal2"): "Via1",
("Metal2", "Metal3"): "Via2",
("Metal3", "Metal4"): "Via3",
("Metal4", "Metal5"): "Via4",
("Metal5", "TopMetal1"): "Via5",
("TopMetal1", "TopMetal2"): "TopVia2",
}
# Via parameters
via_size = 0.26
via_spacing = 0.36
via_enclosure = 0.06
# Calculate via array dimensions
n_vias_x = int((d - 2 * via_enclosure) / via_spacing)
n_vias_y = int((d - 2 * via_enclosure) / via_spacing)
# Add via arrays between consecutive metal layers
for i in range(start_idx, end_idx):
if i < len(metal_stack) - 1:
metal1 = metal_stack[i]
metal2 = metal_stack[i + 1]
via_key = (metal1, metal2)
if via_key in via_mapping:
via_name = via_mapping[via_key]
if via_name in via_layers:
via_layer = via_layers[via_name]
elif via_name == "TopVia2":
via_layer = (125, 5)
else:
continue
# Create via array
for ix in range(n_vias_x):
for iy in range(n_vias_y):
x = -d / 2 + via_enclosure + via_size / 2 + ix * via_spacing
y = -d / 2 + via_enclosure + via_size / 2 + iy * via_spacing
# Check if via is within the pad shape
if shape == "circle":
if math.sqrt(x**2 + y**2) > d / 2 * 0.9:
continue
via = gf.components.rectangle(
size=(via_size, via_size),
layer=via_layer,
centered=True,
)
via_ref = c.add_ref(via)
via_ref.move((x, y))
# Add passivation opening
if shape == "square":
opening = gf.components.rectangle(
size=(d * 0.85, d * 0.85),
layer=passivation_open,
centered=True,
)
c.add_ref(opening)
elif shape == "octagon":
scale = 0.85
opening_vertices = [(x * scale, y * scale) for x, y in vertices]
opening = gf.Component()
opening.add_polygon(opening_vertices, layer=passivation_open)
c.add_ref(opening)
elif shape == "circle":
opening = gf.components.circle(
radius=d / 2 * 0.85,
layer=passivation_open,
)
c.add_ref(opening)
# Add flip-chip bumps if requested
if flip_chip:
# Add under-bump metallization (UBM)
ubm_layer = (155, 0) # Example UBM layer
if shape == "circle":
ubm = gf.components.circle(
radius=d / 2 * 0.7,
layer=ubm_layer,
)
c.add_ref(ubm)
else:
ubm = gf.components.rectangle(
size=(d * 0.7, d * 0.7),
layer=ubm_layer,
centered=True,
)
c.add_ref(ubm)
# Add port at the center
c.add_port(
name="pad",
center=(0, 0),
width=d,
orientation=0,
layer=layers[top_metal],
port_type="electrical",
)
# Add metadata
c.info["shape"] = shape
c.info["diameter"] = diameter
c.info["stack_metals"] = stack_metals
c.info["flip_chip"] = flip_chip
c.info["top_metal"] = top_metal
c.info["bottom_metal"] = bottom_metal
return c
[docs]
@gf.cell
def bondpad_array(
n_pads: int = 4,
pad_pitch: float = 100.0,
pad_diameter: float = 68.0,
shape: Literal["octagon", "square", "circle"] = "octagon",
stack_metals: bool = True,
) -> Component:
"""Create an array of bondpads.
Args:
n_pads: Number of bondpads.
pad_pitch: Pitch between bondpad centers in micrometers.
pad_diameter: Diameter of each bondpad in micrometers.
shape: Shape of the bondpads.
stack_metals: Stack all metal layers.
Returns:
Component with bondpad array.
"""
c = Component()
for i in range(n_pads):
pad = bondpad(
shape=shape,
stack_metals=stack_metals,
diameter=pad_diameter,
)
pad_ref = c.add_ref(pad)
pad_ref.movex(i * pad_pitch)
# Add port for each pad
c.add_port(
name=f"pad_{i + 1}",
center=(i * pad_pitch, 0),
width=pad_diameter,
orientation=0,
layer=pad.ports["pad"].layer,
port_type="electrical",
)
c.info["n_pads"] = n_pads
c.info["pad_pitch"] = pad_pitch
c.info["pad_diameter"] = pad_diameter
return c
if __name__ == "__main__":
# Test the components
c1 = bondpad(shape="octagon")
c1.show()
c2 = bondpad(shape="square", flip_chip=True)
c2.show()
c3 = bondpad_array(n_pads=6)
c3.show()