"""Via stack components for IHP PDK."""
import gdsfactory as gf
from gdsfactory import Component
from gdsfactory.typings import LayerSpec
# Via design rules (in micrometers)
VIA_RULES = {
"Via1": {
"size": 0.26,
"spacing": 0.36,
"enclosure": 0.06,
},
"Via2": {
"size": 0.26,
"spacing": 0.36,
"enclosure": 0.06,
},
"Via3": {
"size": 0.26,
"spacing": 0.36,
"enclosure": 0.06,
},
"Via4": {
"size": 0.26,
"spacing": 0.36,
"enclosure": 0.06,
},
"TopVia1": {
"size": 0.9,
"spacing": 0.9,
"enclosure": 0.3,
},
"TopVia2": {
"size": 5.0,
"spacing": 5.0,
"enclosure": 1.0,
},
}
def get_via_name(bottom_metal: str, top_metal: str) -> str | None:
"""Get the via layer name between two metal layers.
Args:
bottom_metal: Bottom metal layer name.
top_metal: Top metal layer name.
Returns:
Via layer name or None if not adjacent.
"""
via_mapping = {
("Metal1", "Metal2"): "Via1",
("Metal2", "Metal3"): "Via2",
("Metal3", "Metal4"): "Via3",
("Metal4", "Metal5"): "Via4",
("Metal5", "TopMetal1"): "TopVia1",
("TopMetal1", "TopMetal2"): "TopVia2",
}
if (bottom_metal, top_metal) in via_mapping:
return via_mapping[(bottom_metal, top_metal)]
return None
[docs]
@gf.cell
def via_array(
via_type: str = "Via1",
columns: int = 2,
rows: int = 2,
via_size: float | None = None,
via_spacing: float | None = None,
via_enclosure: float | None = None,
layer_via1: LayerSpec = "Via1drawing",
layer_via2: LayerSpec = "Via2drawing",
layer_via3: LayerSpec = "Via3drawing",
layer_via4: LayerSpec = "Via4drawing",
layer_topvia1: LayerSpec = "TopVia1drawing",
layer_topvia2: LayerSpec = "TopVia2drawing",
) -> Component:
"""Create an array of vias.
Args:
via_type: Type of via (Via1, Via2, Via3, Via4, TopVia1, TopVia2).
columns: Number of via columns.
rows: Number of via rows.
via_size: Via size in micrometers (uses default if None).
via_spacing: Via spacing in micrometers (uses default if None).
via_enclosure: Metal enclosure in micrometers (uses default if None).
layer_via1: Via1 layer.
layer_via2: Via2 layer.
layer_via3: Via3 layer.
layer_via4: Via4 layer.
layer_topvia1: TopVia1 layer.
layer_topvia2: TopVia2 layer.
Returns:
Component with via array.
"""
c = Component()
# Map via type to layer parameter
via_layer_map = {
"Via1": layer_via1,
"Via2": layer_via2,
"Via3": layer_via3,
"Via4": layer_via4,
"TopVia1": layer_topvia1,
"TopVia2": layer_topvia2,
}
# Get via parameters
if via_type not in via_layer_map:
raise ValueError(f"Unknown via type: {via_type}")
via_layer = via_layer_map[via_type]
rules = VIA_RULES[via_type]
# Use provided values or defaults
size = via_size if via_size is not None else rules["size"]
spacing = via_spacing if via_spacing is not None else rules["spacing"]
enclosure = via_enclosure if via_enclosure is not None else rules["enclosure"]
# Create via array
for col in range(columns):
for row in range(rows):
x = col * spacing
y = row * spacing
via = gf.components.rectangle(
size=(size, size),
layer=via_layer,
)
via_ref = c.add_ref(via)
via_ref.move((x, y))
# Calculate total dimensions
array_width = size if columns == 1 else (columns - 1) * spacing + size
array_height = size if rows == 1 else (rows - 1) * spacing + size
# Add metadata
c.info["via_type"] = via_type
c.info["columns"] = columns
c.info["rows"] = rows
c.info["array_width"] = array_width
c.info["array_height"] = array_height
c.info["enclosure_width"] = array_width + 2 * enclosure
c.info["enclosure_height"] = array_height + 2 * enclosure
return c
[docs]
@gf.cell
def via_stack(
bottom_layer: str = "Metal1",
top_layer: str = "Metal2",
size: tuple[float, float] = (10.0, 10.0),
vn_columns: int = 2,
vn_rows: int = 2,
vt1_columns: int = 1,
vt1_rows: int = 1,
vt2_columns: int = 1,
vt2_rows: int = 1,
layer_metal1: LayerSpec = "Metal1drawing",
layer_metal2: LayerSpec = "Metal2drawing",
layer_metal3: LayerSpec = "Metal3drawing",
layer_metal4: LayerSpec = "Metal4drawing",
layer_metal5: LayerSpec = "Metal5drawing",
layer_topmetal1: LayerSpec = "TopMetal1drawing",
layer_topmetal2: LayerSpec = "TopMetal2drawing",
layer_via1: LayerSpec = "Via1drawing",
layer_via2: LayerSpec = "Via2drawing",
layer_via3: LayerSpec = "Via3drawing",
layer_via4: LayerSpec = "Via4drawing",
layer_topvia1: LayerSpec = "TopVia1drawing",
layer_topvia2: LayerSpec = "TopVia2drawing",
) -> Component:
"""Create a via stack connecting multiple metal layers.
Args:
bottom_layer: Bottom metal layer name.
top_layer: Top metal layer name.
size: Size of the metal stack (width, height) in micrometers.
vn_columns: Number of columns for normal vias (Via1-Via4).
vn_rows: Number of rows for normal vias.
vt1_columns: Number of columns for TopVia1.
vt1_rows: Number of rows for TopVia1.
vt2_columns: Number of columns for TopVia2.
vt2_rows: Number of rows for TopVia2.
layer_metal1: Metal1 layer.
layer_metal2: Metal2 layer.
layer_metal3: Metal3 layer.
layer_metal4: Metal4 layer.
layer_metal5: Metal5 layer.
layer_topmetal1: TopMetal1 layer.
layer_topmetal2: TopMetal2 layer.
layer_via1: Via1 layer.
layer_via2: Via2 layer.
layer_via3: Via3 layer.
layer_via4: Via4 layer.
layer_topvia1: TopVia1 layer.
layer_topvia2: TopVia2 layer.
Returns:
Component with via stack.
"""
c = Component()
# Map metal names to layer parameters
metal_layer_map = {
"Metal1": layer_metal1,
"Metal2": layer_metal2,
"Metal3": layer_metal3,
"Metal4": layer_metal4,
"Metal5": layer_metal5,
"TopMetal1": layer_topmetal1,
"TopMetal2": layer_topmetal2,
}
# Validate layers
metal_order = [
"Metal1",
"Metal2",
"Metal3",
"Metal4",
"Metal5",
"TopMetal1",
"TopMetal2",
]
if bottom_layer not in metal_order or top_layer not in metal_order:
raise ValueError(f"Invalid metal layers: {bottom_layer}, {top_layer}")
bottom_idx = metal_order.index(bottom_layer)
top_idx = metal_order.index(top_layer)
if bottom_idx >= top_idx:
raise ValueError(
f"Bottom layer must be below top layer: {bottom_layer} -> {top_layer}"
)
width, height = size
# Add metal layers
for idx in range(bottom_idx, top_idx + 1):
metal_name = metal_order[idx]
metal_layer = metal_layer_map[metal_name]
metal = gf.components.rectangle(
size=(width, height),
layer=metal_layer,
centered=True,
)
c.add_ref(metal)
# Add vias between layers
for idx in range(bottom_idx, top_idx):
bottom_metal = metal_order[idx]
top_metal = metal_order[idx + 1]
via_name = get_via_name(bottom_metal, top_metal)
if via_name:
rules = VIA_RULES[via_name]
via_size = rules["size"]
via_spacing = rules["spacing"]
via_enclosure = rules["enclosure"]
# Determine number of vias based on type
if "TopVia" in via_name:
if via_name == "TopVia1":
columns = vt1_columns
rows = vt1_rows
else: # TopVia2
columns = vt2_columns
rows = vt2_rows
else:
columns = vn_columns
rows = vn_rows
# Calculate maximum number of vias that fit
max_columns = int((width - 2 * via_enclosure - via_size) / via_spacing) + 1
max_rows = int((height - 2 * via_enclosure - via_size) / via_spacing) + 1
# Use minimum of requested and maximum
actual_columns = min(columns, max_columns)
actual_rows = min(rows, max_rows)
if actual_columns > 0 and actual_rows > 0:
# Create via array
via_array_comp = via_array(
via_type=via_name,
columns=actual_columns,
rows=actual_rows,
via_size=via_size,
via_spacing=via_spacing,
via_enclosure=via_enclosure,
layer_via1=layer_via1,
layer_via2=layer_via2,
layer_via3=layer_via3,
layer_via4=layer_via4,
layer_topvia1=layer_topvia1,
layer_topvia2=layer_topvia2,
)
# Center the via array
array_width = via_array_comp.info["array_width"]
array_height = via_array_comp.info["array_height"]
via_ref = c.add_ref(via_array_comp)
via_ref.move((-array_width / 2, -array_height / 2))
# Add ports
c.add_port(
name="bottom",
center=(0, 0),
width=width,
orientation=0,
layer=metal_layer_map[bottom_layer],
port_type="electrical",
)
c.add_port(
name="top",
center=(0, 0),
width=width,
orientation=0,
layer=metal_layer_map[top_layer],
port_type="electrical",
)
# Add metadata
c.info["bottom_layer"] = bottom_layer
c.info["top_layer"] = top_layer
c.info["width"] = width
c.info["height"] = height
c.info["n_layers"] = top_idx - bottom_idx + 1
return c
[docs]
@gf.cell
def via_stack_with_pads(
bottom_layer: str = "Metal1",
top_layer: str = "TopMetal2",
size: tuple[float, float] = (10.0, 10.0),
pad_size: tuple[float, float] = (20.0, 20.0),
pad_spacing: float = 50.0,
layer_metal1: LayerSpec = "Metal1drawing",
layer_metal2: LayerSpec = "Metal2drawing",
layer_metal3: LayerSpec = "Metal3drawing",
layer_metal4: LayerSpec = "Metal4drawing",
layer_metal5: LayerSpec = "Metal5drawing",
layer_topmetal1: LayerSpec = "TopMetal1drawing",
layer_topmetal2: LayerSpec = "TopMetal2drawing",
layer_via1: LayerSpec = "Via1drawing",
layer_via2: LayerSpec = "Via2drawing",
layer_via3: LayerSpec = "Via3drawing",
layer_via4: LayerSpec = "Via4drawing",
layer_topvia1: LayerSpec = "TopVia1drawing",
layer_topvia2: LayerSpec = "TopVia2drawing",
) -> Component:
"""Create a via stack with test pads.
Args:
bottom_layer: Bottom metal layer name.
top_layer: Top metal layer name.
size: Size of the via stack (width, height) in micrometers.
pad_size: Size of the test pads (width, height) in micrometers.
pad_spacing: Spacing between pads in micrometers.
layer_metal1: Metal1 layer.
layer_metal2: Metal2 layer.
layer_metal3: Metal3 layer.
layer_metal4: Metal4 layer.
layer_metal5: Metal5 layer.
layer_topmetal1: TopMetal1 layer.
layer_topmetal2: TopMetal2 layer.
layer_via1: Via1 layer.
layer_via2: Via2 layer.
layer_via3: Via3 layer.
layer_via4: Via4 layer.
layer_topvia1: TopVia1 layer.
layer_topvia2: TopVia2 layer.
Returns:
Component with via stack and test pads.
"""
c = Component()
# Map metal names to layer parameters
metal_layer_map = {
"Metal1": layer_metal1,
"Metal2": layer_metal2,
"Metal3": layer_metal3,
"Metal4": layer_metal4,
"Metal5": layer_metal5,
"TopMetal1": layer_topmetal1,
"TopMetal2": layer_topmetal2,
}
# Create via stack
stack = via_stack(
bottom_layer=bottom_layer,
top_layer=top_layer,
size=size,
layer_metal1=layer_metal1,
layer_metal2=layer_metal2,
layer_metal3=layer_metal3,
layer_metal4=layer_metal4,
layer_metal5=layer_metal5,
layer_topmetal1=layer_topmetal1,
layer_topmetal2=layer_topmetal2,
layer_via1=layer_via1,
layer_via2=layer_via2,
layer_via3=layer_via3,
layer_via4=layer_via4,
layer_topvia1=layer_topvia1,
layer_topvia2=layer_topvia2,
)
c.add_ref(stack)
# Add bottom pad
bottom_pad = gf.components.rectangle(
size=pad_size,
layer=metal_layer_map[bottom_layer],
centered=True,
)
bottom_pad_ref = c.add_ref(bottom_pad)
bottom_pad_ref.movex(-pad_spacing / 2)
# Add top pad
top_pad = gf.components.rectangle(
size=pad_size,
layer=metal_layer_map[top_layer],
centered=True,
)
top_pad_ref = c.add_ref(top_pad)
top_pad_ref.movex(pad_spacing / 2)
# Connect pads to stack
bottom_trace = gf.components.rectangle(
size=(pad_spacing / 2 - size[0] / 2, 2.0),
layer=metal_layer_map[bottom_layer],
)
bottom_trace_ref = c.add_ref(bottom_trace)
bottom_trace_ref.move((-pad_spacing / 2, -1.0))
top_trace = gf.components.rectangle(
size=(pad_spacing / 2 - size[0] / 2, 2.0),
layer=metal_layer_map[top_layer],
)
top_trace_ref = c.add_ref(top_trace)
top_trace_ref.move((size[0] / 2, -1.0))
# Add ports
c.add_port(
name="pad1",
center=(-pad_spacing / 2, 0),
width=pad_size[1],
orientation=180,
layer=metal_layer_map[bottom_layer],
port_type="electrical",
)
c.add_port(
name="pad2",
center=(pad_spacing / 2, 0),
width=pad_size[1],
orientation=0,
layer=metal_layer_map[top_layer],
port_type="electrical",
)
return c
if __name__ == "__main__":
from gdsfactory.difftest import xor
from ihp import PDK, cells
PDK.activate()
# Test the components
# Note: via_array is not in cells, so we'll test via_stack
# c0 = cells.via_array() # original
# c1 = via_array() # New
# # c = gf.grid([c0, c1], spacing=100)
# c = xor(c0, c1)
# c.show()
c0 = cells.ViaStack() # original
c1 = via_stack() # New
c = xor(c0, c1)
c.show()
# c0 = cells.via_stack_with_pads() # original
# c1 = via_stack_with_pads() # New
# # c = gf.grid([c0, c1], spacing=100)
# c = xor(c0, c1)
# c.show()