Source code for ihp.cells.passives

"""Passive components (varicaps, ESD, taps, seal rings) for IHP PDK."""

import gdsfactory as gf
from gdsfactory import Component

# Define layers
LAYERS = {
    "NWell": (31, 0),
    "PWell": (29, 0),
    "Activ": (1, 0),
    "GatPoly": (5, 0),
    "pSD": (14, 0),
    "nSD": (16, 0),
    "Ptap": (13, 0),
    "Ntap": (26, 0),
    "Cont": (6, 0),
    "Metal1": (8, 0),
    "Metal2": (10, 0),
    "Metal3": (30, 0),
    "Metal4": (50, 0),
    "Metal5": (67, 0),
    "TopMetal1": (126, 5),
    "TopMetal2": (134, 5),
    "Via1": (19, 0),
    "Via2": (29, 0),
    "Via3": (49, 0),
    "Via4": (66, 0),
    "TopVia1": (125, 5),
    "TopVia2": (133, 5),
    "Varicap": (87, 0),
    "ESD": (88, 0),
    "SealRing": (167, 5),
    "TEXT": (63, 63),
}


[docs] @gf.cell def svaricap( width: float = 1.0, length: float = 1.0, nf: int = 1, model: str = "svaricap", ) -> Component: """Create a MOS varicap (variable capacitor). Args: width: Width of the varicap in micrometers. length: Length of the varicap in micrometers. nf: Number of fingers. model: Device model name. Returns: Component with varicap layout. """ c = Component() # Design rules var_min_width = 0.5 var_min_length = 0.5 gate_ext = 0.18 active_ext = 0.23 cont_size = 0.16 cont_enc = 0.07 nwell_enc = 0.31 # Validate dimensions width = max(width, var_min_width) length = max(length, var_min_length) # Grid snap grid = 0.005 width = round(width / grid) * grid length = round(length / grid) * grid # Calculate finger dimensions finger_width = width / nf finger_pitch = finger_width + 0.5 # N-Well nwell = gf.components.rectangle( size=( length + 2 * active_ext + 2 * nwell_enc, nf * finger_pitch + 2 * nwell_enc, ), layer=LAYERS["NWell"], centered=True, ) c.add_ref(nwell) # Create varicap fingers for i in range(nf): y_offset = (i - nf / 2 + 0.5) * finger_pitch # Gate poly (acts as one terminal) gate = gf.components.rectangle( size=(length, finger_width + 2 * gate_ext), layer=LAYERS["GatPoly"], ) gate_ref = c.add_ref(gate) gate_ref.move((-length / 2, y_offset - finger_width / 2 - gate_ext)) # Active region (acts as other terminal) active = gf.components.rectangle( size=(length + 2 * active_ext, finger_width), layer=LAYERS["Activ"], ) active_ref = c.add_ref(active) active_ref.move((-length / 2 - active_ext, y_offset - finger_width / 2)) # N+ implant for active region nsd = gf.components.rectangle( size=(length + 2 * active_ext, finger_width), layer=LAYERS["nSD"], ) nsd_ref = c.add_ref(nsd) nsd_ref.move((-length / 2 - active_ext, y_offset - finger_width / 2)) # Contacts on active regions (source/drain) # Left side contacts cont_left = gf.components.rectangle( size=(cont_size, cont_size), layer=LAYERS["Cont"], ) cont_left_ref = c.add_ref(cont_left) cont_left_ref.move( (-length / 2 - active_ext + cont_enc, y_offset - cont_size / 2) ) # Right side contacts cont_right = gf.components.rectangle( size=(cont_size, cont_size), layer=LAYERS["Cont"], ) cont_right_ref = c.add_ref(cont_right) cont_right_ref.move( (length / 2 + active_ext - cont_enc - cont_size, y_offset - cont_size / 2) ) # Metal connections # Gate connection (Metal1) gate_metal = gf.components.rectangle( size=(1.0, nf * finger_pitch), layer=LAYERS["Metal1"], ) gate_metal_ref = c.add_ref(gate_metal) gate_metal_ref.move((-length / 2 - 1.5, -nf * finger_pitch / 2)) # Active connection (Metal1) active_metal = gf.components.rectangle( size=(1.0, nf * finger_pitch), layer=LAYERS["Metal1"], ) active_metal_ref = c.add_ref(active_metal) active_metal_ref.move((length / 2 + 0.5, -nf * finger_pitch / 2)) # Varicap marker var_mark = gf.components.rectangle( size=(length + 2 * active_ext + 0.5, nf * finger_pitch + 0.5), layer=LAYERS["Varicap"], centered=True, ) c.add_ref(var_mark) # Add ports c.add_port( name="G", center=(-length / 2 - 1.0, 0), width=nf * finger_pitch, orientation=180, layer=LAYERS["Metal1"], port_type="electrical", ) c.add_port( name="B", center=(length / 2 + 1.0, 0), width=nf * finger_pitch, orientation=0, layer=LAYERS["Metal1"], port_type="electrical", ) # Add metadata c.info["model"] = model c.info["width"] = width c.info["length"] = length c.info["nf"] = nf c.info["type"] = "varicap" return c
[docs] @gf.cell def esd_nmos( width: float = 50.0, length: float = 0.5, nf: int = 10, model: str = "esd_nmos", ) -> Component: """Create an ESD protection NMOS device. Args: width: Total width of the ESD device in micrometers. length: Gate length in micrometers. nf: Number of fingers. model: Device model name. Returns: Component with ESD NMOS layout. """ c = Component() # Design rules for ESD devices gate_width = width / nf gate_length = length gate_ext = 0.18 active_ext = 0.3 # Larger for ESD cont_size = 0.16 cont_spacing = 0.18 cont_enc = 0.07 metal_enc = 0.06 pwell_enc = 0.5 # Grid snap grid = 0.005 gate_width = round(gate_width / grid) * grid gate_length = round(gate_length / grid) * grid # P-Well for ESD NMOS pwell = gf.components.rectangle( size=( (gate_length + 2 * active_ext) * nf + pwell_enc * 2, gate_width + 2 * gate_ext + pwell_enc * 2, ), layer=LAYERS["PWell"], centered=True, ) c.add_ref(pwell) # Create multi-finger ESD structure finger_pitch = gate_length + 2 * active_ext + 0.5 for i in range(nf): x_offset = (i - nf / 2 + 0.5) * finger_pitch # Gate poly gate = gf.components.rectangle( size=(gate_length, gate_width + 2 * gate_ext), layer=LAYERS["GatPoly"], ) gate_ref = c.add_ref(gate) gate_ref.move((x_offset - gate_length / 2, -gate_width / 2 - gate_ext)) # Active region active = gf.components.rectangle( size=(gate_length + 2 * active_ext, gate_width), layer=LAYERS["Activ"], ) active_ref = c.add_ref(active) active_ref.move((x_offset - gate_length / 2 - active_ext, -gate_width / 2)) # N+ implant nsd = gf.components.rectangle( size=(gate_length + 2 * active_ext, gate_width), layer=LAYERS["nSD"], ) nsd_ref = c.add_ref(nsd) nsd_ref.move((x_offset - gate_length / 2 - active_ext, -gate_width / 2)) # Source/Drain contacts n_cont_y = int((gate_width - cont_size) / cont_spacing) + 1 for j in range(n_cont_y): y_pos = -gate_width / 2 + cont_enc + j * cont_spacing # Source contact cont_s = gf.components.rectangle( size=(cont_size, cont_size), layer=LAYERS["Cont"], ) cont_s_ref = c.add_ref(cont_s) cont_s_ref.move((x_offset - gate_length / 2 - active_ext + cont_enc, y_pos)) # Drain contact cont_d = gf.components.rectangle( size=(cont_size, cont_size), layer=LAYERS["Cont"], ) cont_d_ref = c.add_ref(cont_d) cont_d_ref.move((x_offset + gate_length / 2 + cont_enc, y_pos)) # Metal bus connections # Source bus (connected to ground) source_bus = gf.components.rectangle( size=(nf * finger_pitch, gate_width + 2 * metal_enc), layer=LAYERS["Metal1"], ) source_bus_ref = c.add_ref(source_bus) source_bus_ref.move((-nf * finger_pitch / 2, -gate_width / 2 - metal_enc)) # Drain bus (connected to I/O pad) drain_bus = gf.components.rectangle( size=(nf * finger_pitch, 1.0), layer=LAYERS["Metal2"], ) drain_bus_ref = c.add_ref(drain_bus) drain_bus_ref.move((-nf * finger_pitch / 2, gate_width / 2 + 1.0)) # Gate bus (can be tied to source or left floating) gate_bus = gf.components.rectangle( size=(nf * finger_pitch, 0.5), layer=LAYERS["GatPoly"], ) gate_bus_ref = c.add_ref(gate_bus) gate_bus_ref.move((-nf * finger_pitch / 2, -gate_width / 2 - gate_ext - 0.5)) # ESD marker esd_mark = gf.components.rectangle( size=(nf * finger_pitch + 1.0, gate_width + 3.0), layer=LAYERS["ESD"], centered=True, ) c.add_ref(esd_mark) # Add ports c.add_port( name="PAD", center=(0, gate_width / 2 + 1.5), width=nf * finger_pitch, orientation=90, layer=LAYERS["Metal2"], port_type="electrical", ) c.add_port( name="GND", center=(0, -gate_width / 2), width=nf * finger_pitch, orientation=270, layer=LAYERS["Metal1"], port_type="electrical", ) # Add metadata c.info["model"] = model c.info["width"] = width c.info["length"] = length c.info["nf"] = nf c.info["type"] = "esd_nmos" return c
[docs] @gf.cell def ptap1( width: float = 1.0, length: float = 1.0, rows: int = 1, cols: int = 1, ) -> Component: """Create a P+ substrate tap. Args: width: Width of the tap in micrometers. length: Length of the tap in micrometers. rows: Number of contact rows. cols: Number of contact columns. Returns: Component with P+ tap layout. """ c = Component() # Design rules cont_size = 0.16 cont_spacing = 0.18 metal_enc = 0.06 tap_enc = 0.1 # Grid snap grid = 0.005 width = round(width / grid) * grid length = round(length / grid) * grid # P+ active region active = gf.components.rectangle( size=(length, width), layer=LAYERS["Activ"], centered=True, ) c.add_ref(active) # P+ implant psd = gf.components.rectangle( size=(length + 2 * tap_enc, width + 2 * tap_enc), layer=LAYERS["pSD"], centered=True, ) c.add_ref(psd) # P-tap marker ptap = gf.components.rectangle( size=(length, width), layer=LAYERS["Ptap"], centered=True, ) c.add_ref(ptap) # Contact array cont_array_width = cont_size * cols + cont_spacing * (cols - 1) cont_array_height = cont_size * rows + cont_spacing * (rows - 1) for i in range(cols): for j in range(rows): x = -cont_array_width / 2 + cont_size / 2 + i * (cont_size + cont_spacing) y = -cont_array_height / 2 + cont_size / 2 + j * (cont_size + cont_spacing) cont = gf.components.rectangle( size=(cont_size, cont_size), layer=LAYERS["Cont"], centered=True, ) cont_ref = c.add_ref(cont) cont_ref.move((x, y)) # Metal1 connection metal = gf.components.rectangle( size=(cont_array_width + 2 * metal_enc, cont_array_height + 2 * metal_enc), layer=LAYERS["Metal1"], centered=True, ) c.add_ref(metal) # Add port c.add_port( name="TAP", center=(0, 0), width=width, orientation=0, layer=LAYERS["Metal1"], port_type="electrical", ) # Add metadata c.info["type"] = "ptap" c.info["width"] = width c.info["length"] = length c.info["rows"] = rows c.info["cols"] = cols return c
[docs] @gf.cell def ntap1( width: float = 1.0, length: float = 1.0, rows: int = 1, cols: int = 1, ) -> Component: """Create an N+ substrate tap. Args: width: Width of the tap in micrometers. length: Length of the tap in micrometers. rows: Number of contact rows. cols: Number of contact columns. Returns: Component with N+ tap layout. """ c = Component() # Design rules cont_size = 0.16 cont_spacing = 0.18 metal_enc = 0.06 tap_enc = 0.1 nwell_enc = 0.31 # Grid snap grid = 0.005 width = round(width / grid) * grid length = round(length / grid) * grid # N-Well nwell = gf.components.rectangle( size=(length + 2 * nwell_enc, width + 2 * nwell_enc), layer=LAYERS["NWell"], centered=True, ) c.add_ref(nwell) # N+ active region active = gf.components.rectangle( size=(length, width), layer=LAYERS["Activ"], centered=True, ) c.add_ref(active) # N+ implant nsd = gf.components.rectangle( size=(length + 2 * tap_enc, width + 2 * tap_enc), layer=LAYERS["nSD"], centered=True, ) c.add_ref(nsd) # N-tap marker ntap = gf.components.rectangle( size=(length, width), layer=LAYERS["Ntap"], centered=True, ) c.add_ref(ntap) # Contact array cont_array_width = cont_size * cols + cont_spacing * (cols - 1) cont_array_height = cont_size * rows + cont_spacing * (rows - 1) for i in range(cols): for j in range(rows): x = -cont_array_width / 2 + cont_size / 2 + i * (cont_size + cont_spacing) y = -cont_array_height / 2 + cont_size / 2 + j * (cont_size + cont_spacing) cont = gf.components.rectangle( size=(cont_size, cont_size), layer=LAYERS["Cont"], centered=True, ) cont_ref = c.add_ref(cont) cont_ref.move((x, y)) # Metal1 connection metal = gf.components.rectangle( size=(cont_array_width + 2 * metal_enc, cont_array_height + 2 * metal_enc), layer=LAYERS["Metal1"], centered=True, ) c.add_ref(metal) # Add port c.add_port( name="TAP", center=(0, 0), width=width, orientation=0, layer=LAYERS["Metal1"], port_type="electrical", ) # Add metadata c.info["type"] = "ntap" c.info["width"] = width c.info["length"] = length c.info["rows"] = rows c.info["cols"] = cols return c
[docs] @gf.cell def sealring( width: float = 200.0, height: float = 200.0, ring_width: float = 5.0, ) -> Component: """Create a seal ring for die protection. Args: width: Inner width of the seal ring in micrometers. height: Inner height of the seal ring in micrometers. ring_width: Width of the seal ring metal in micrometers. Returns: Component with seal ring layout. """ c = Component() # Create seal ring on all metal layers metal_layers = [ LAYERS["Metal1"], LAYERS["Metal2"], LAYERS["Metal3"], LAYERS["Metal4"], LAYERS["Metal5"], LAYERS["TopMetal1"], LAYERS["TopMetal2"], ] # Create ring on each metal layer for metal_layer in metal_layers: # Outer rectangle outer = gf.components.rectangle( size=(width + 2 * ring_width, height + 2 * ring_width), layer=metal_layer, centered=True, ) # Inner rectangle (to create ring) inner = gf.components.rectangle( size=(width, height), layer=metal_layer, centered=True, ) # Create ring by boolean subtraction ring = gf.boolean(outer, inner, "A-B", layer=metal_layer) c.add_ref(ring) # Add vias between metal layers via_layers = [ LAYERS["Via1"], LAYERS["Via2"], LAYERS["Via3"], LAYERS["Via4"], LAYERS["TopVia1"], LAYERS["TopVia2"], ] # Via arrays in the ring via_size = 0.26 via_spacing = 0.36 for via_layer in via_layers: # Calculate number of vias along each edge n_vias_x = int((width + ring_width - via_size) / via_spacing) n_vias_y = int((height + ring_width - via_size) / via_spacing) # Top edge vias for i in range(n_vias_x): x = -width / 2 - ring_width / 2 + via_size / 2 + i * via_spacing y = height / 2 + ring_width / 2 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)) # Bottom edge vias for i in range(n_vias_x): x = -width / 2 - ring_width / 2 + via_size / 2 + i * via_spacing y = -height / 2 - ring_width / 2 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)) # Left edge vias for i in range(n_vias_y): x = -width / 2 - ring_width / 2 y = -height / 2 - ring_width / 2 + via_size / 2 + i * via_spacing 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)) # Right edge vias for i in range(n_vias_y): x = width / 2 + ring_width / 2 y = -height / 2 - ring_width / 2 + via_size / 2 + i * via_spacing 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)) # Seal ring marker seal_mark = gf.components.rectangle( size=(width + 2 * ring_width + 1.0, height + 2 * ring_width + 1.0), layer=LAYERS["SealRing"], centered=True, ) seal_inner = gf.components.rectangle( size=(width - 1.0, height - 1.0), layer=LAYERS["SealRing"], centered=True, ) seal_ring_mark = gf.boolean(seal_mark, seal_inner, "A-B", layer=LAYERS["SealRing"]) c.add_ref(seal_ring_mark) # Add metadata c.info["type"] = "sealring" c.info["width"] = width c.info["height"] = height c.info["ring_width"] = ring_width return c
if __name__ == "__main__": # Test the components c1 = svaricap(width=2.0, length=1.0, nf=4) c1.show() c2 = esd_nmos(width=100.0, length=0.5, nf=20) c2.show() c3 = ptap1(width=2.0, length=2.0, rows=2, cols=2) c3.show() c4 = sealring(width=500, height=500, ring_width=10) c4.show()