Source code for ihp.cells.via_stacks

"""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 = {
    "Cont": {
        "size": 0.16,
        "spacing": 0.18,
        "enclosure": 0.06,
    },
    "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,
    },
    "Vmim": {
        "size": 0.42,
        "spacing": 0.47,
        "enclosure": 0.42,
    },
    "TopVia1": {
        "size": 0.42,
        "spacing": 0.42,
        "enclosure": 0.3,
    },
    "TopVia2": {
        "size": 0.9,
        "spacing": 1.06,
        "enclosure": 0.5,
    },
}


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 = {
        ("Activ", "Metal1"): "Cont",
        ("GatPoly", "Metal1"): "Cont",
        ("Metal1", "Metal2"): "Via1",
        ("Metal2", "Metal3"): "Via2",
        ("Metal3", "Metal4"): "Via3",
        ("Metal4", "Metal5"): "Via4",
        ("MIM", "TopMetal1"): "Vmim",
        ("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_cont: LayerSpec = "Contdrawing", layer_via1: LayerSpec = "Via1drawing", layer_via2: LayerSpec = "Via2drawing", layer_via3: LayerSpec = "Via3drawing", layer_via4: LayerSpec = "Via4drawing", layer_vmim: LayerSpec = "Vmimdrawing", 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 = { "Cont": layer_cont, "Via1": layer_via1, "Via2": layer_via2, "Via3": layer_via3, "Via4": layer_via4, "Vmim": layer_vmim, "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_activ: LayerSpec = "Activdrawing", layer_gatpoly: LayerSpec = "GatPolydrawing", 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_activ_pin: LayerSpec = "Activpin", layer_gatpoly_pin: LayerSpec = "GatPolypin", layer_metal1_pin: LayerSpec = "Metal1pin", layer_metal2_pin: LayerSpec = "Metal2pin", layer_metal3_pin: LayerSpec = "Metal3pin", layer_metal4_pin: LayerSpec = "Metal4pin", layer_metal5_pin: LayerSpec = "Metal5pin", layer_topmetal1_pin: LayerSpec = "TopMetal1pin", layer_topmetal2_pin: LayerSpec = "TopMetal2pin", layer_cont: LayerSpec = "Contdrawing", 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. bottom_layer can be Activ, GatPoly, or any metal (Metal1-TopMetal2). Activ and GatPoly connect to Metal1 through Cont; they are independent paths and must not appear together in the same stack. Args: bottom_layer: Bottom layer name (Activ, GatPoly, or Metal1-TopMetal2). top_layer: Top metal layer name (Metal1-TopMetal2). size: Size of the metal stack (width, height) in micrometers. vn_columns: Number of columns for normal vias (Cont, 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. Returns: Component with via stack. """ c = Component() # BEOL metal stack (Metal1 and above) _beol_order = [ "Metal1", "Metal2", "Metal3", "Metal4", "Metal5", "TopMetal1", "TopMetal2", ] # Sub-Metal1 layers that connect to Metal1 via Cont _sub_metal1 = {"Activ", "GatPoly"} # Map layer names to layer parameters layer_map = { "Activ": layer_activ, "GatPoly": layer_gatpoly, "Metal1": layer_metal1, "Metal2": layer_metal2, "Metal3": layer_metal3, "Metal4": layer_metal4, "Metal5": layer_metal5, "TopMetal1": layer_topmetal1, "TopMetal2": layer_topmetal2, } pin_layer_map = { "Activ": layer_activ_pin, "GatPoly": layer_gatpoly_pin, "Metal1": layer_metal1_pin, "Metal2": layer_metal2_pin, "Metal3": layer_metal3_pin, "Metal4": layer_metal4_pin, "Metal5": layer_metal5_pin, "TopMetal1": layer_topmetal1_pin, "TopMetal2": layer_topmetal2_pin, } # Build effective layer order based on bottom_layer if bottom_layer in _sub_metal1: # Activ or GatPoly -> Cont -> Metal1 -> ... -> top_layer if top_layer in _sub_metal1: raise ValueError( f"Cannot stack between two sub-Metal1 layers: " f"{bottom_layer} -> {top_layer}" ) if top_layer not in _beol_order: raise ValueError(f"Invalid top layer: {top_layer}") top_idx = _beol_order.index(top_layer) layer_order = [bottom_layer] + _beol_order[: top_idx + 1] else: if bottom_layer not in _beol_order: raise ValueError(f"Invalid bottom layer: {bottom_layer}") if top_layer not in _beol_order: raise ValueError(f"Invalid top layer: {top_layer}") bottom_idx = _beol_order.index(bottom_layer) top_idx = _beol_order.index(top_layer) if bottom_idx >= top_idx: raise ValueError( f"Bottom layer must be below top layer: {bottom_layer} -> {top_layer}" ) layer_order = _beol_order[bottom_idx : top_idx + 1] width, height = size # Add conductor layers for name in layer_order: metal = gf.components.rectangle( size=(width, height), layer=layer_map[name], centered=True, ) c.add_ref(metal) # Add vias between adjacent layers for i in range(len(layer_order) - 1): bot = layer_order[i] top = layer_order[i + 1] via_name = get_via_name(bot, top) if via_name is None: continue 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 via_name == "TopVia1": columns = vt1_columns rows = vt1_rows elif via_name == "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: 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_cont=layer_cont, 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=pin_layer_map[bottom_layer], port_type="electrical", ) c.add_port( name="top", center=(0, 0), width=width, orientation=0, layer=pin_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"] = len(layer_order) 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_activ: LayerSpec = "Activdrawing", layer_gatpoly: LayerSpec = "GatPolydrawing", 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_activ_pin: LayerSpec = "Activpin", layer_gatpoly_pin: LayerSpec = "GatPolypin", layer_metal1_pin: LayerSpec = "Metal1pin", layer_metal2_pin: LayerSpec = "Metal2pin", layer_metal3_pin: LayerSpec = "Metal3pin", layer_metal4_pin: LayerSpec = "Metal4pin", layer_metal5_pin: LayerSpec = "Metal5pin", layer_topmetal1_pin: LayerSpec = "TopMetal1pin", layer_topmetal2_pin: LayerSpec = "TopMetal2pin", layer_cont: LayerSpec = "Contdrawing", 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 layer name (Activ, GatPoly, or Metal1-TopMetal2). top_layer: Top metal layer name (Metal1-TopMetal2). 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. Returns: Component with via stack and test pads. """ c = Component() # Map layer names to layer parameters layer_map = { "Activ": layer_activ, "GatPoly": layer_gatpoly, "Metal1": layer_metal1, "Metal2": layer_metal2, "Metal3": layer_metal3, "Metal4": layer_metal4, "Metal5": layer_metal5, "TopMetal1": layer_topmetal1, "TopMetal2": layer_topmetal2, } pin_layer_map = { "Activ": layer_activ_pin, "GatPoly": layer_gatpoly_pin, "Metal1": layer_metal1_pin, "Metal2": layer_metal2_pin, "Metal3": layer_metal3_pin, "Metal4": layer_metal4_pin, "Metal5": layer_metal5_pin, "TopMetal1": layer_topmetal1_pin, "TopMetal2": layer_topmetal2_pin, } # Create via stack stack = via_stack( bottom_layer=bottom_layer, top_layer=top_layer, size=size, layer_activ=layer_activ, layer_gatpoly=layer_gatpoly, 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_activ_pin=layer_activ_pin, layer_gatpoly_pin=layer_gatpoly_pin, layer_metal1_pin=layer_metal1_pin, layer_metal2_pin=layer_metal2_pin, layer_metal3_pin=layer_metal3_pin, layer_metal4_pin=layer_metal4_pin, layer_metal5_pin=layer_metal5_pin, layer_topmetal1_pin=layer_topmetal1_pin, layer_topmetal2_pin=layer_topmetal2_pin, layer_cont=layer_cont, 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=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=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=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=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=pin_layer_map[bottom_layer], port_type="electrical", ) c.add_port( name="pad2", center=(pad_spacing / 2, 0), width=pad_size[1], orientation=0, layer=pin_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()