Source code for ihp.cells.passives

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

from typing import Literal, TypeAlias

import gdsfactory as gf
import numpy as np
from gdsfactory import Component
from gdsfactory.typings import LayerSpec
from numpy import floor, round

from ihp import cells, tech

FloatLike: TypeAlias = np.float32 | np.float64 | float
Point: TypeAlias = tuple[FloatLike, FloatLike]


[docs] @gf.cell def svaricap( width: float = 1.0, length: float = 1.0, nf: int = 1, model: str = "sg13_hv_svaricap", layer_nwell: LayerSpec = "NWelldrawing", layer_activ: LayerSpec = "Activdrawing", layer_gatpoly: LayerSpec = "GatPolydrawing", layer_nsd: LayerSpec = "nSDdrawing", layer_cont: LayerSpec = "Contdrawing", layer_metal1: LayerSpec = "Metal1drawing", layer_varicap: LayerSpec = "Varicapdrawing", ) -> 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. layer_nwell: N-well layer. layer_activ: Active region layer. layer_gatpoly: Gate polysilicon layer. layer_nsd: N+ source/drain doping layer. layer_cont: Contact layer. layer_metal1: Metal1 layer. layer_varicap: Varicap marker layer. 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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_metal1, port_type="electrical", ) c.add_port( name="B", center=(length / 2 + 1.0, 0), width=nf * finger_pitch, orientation=0, layer=layer_metal1, port_type="electrical", ) # Add VLSIR metadata c.info["vlsir"] = { "model": model, "spice_type": "SUBCKT", "spice_lib": "sg13g2_svaricaphv_mod.lib", "port_order": ["G1", "W", "G2", "bn"], "port_map": {}, "params": {"w": width * 1e-6, "l": length * 1e-6, "Nx": nf}, } return c
[docs] @gf.cell def esd_nmos( width: float = 50.0, length: float = 0.5, nf: int = 10, model: str = "nmoscl_2", layer_pwell: LayerSpec = "PWelldrawing", layer_activ: LayerSpec = "Activdrawing", layer_gatpoly: LayerSpec = "GatPolydrawing", layer_nsd: LayerSpec = "nSDdrawing", layer_cont: LayerSpec = "Contdrawing", layer_metal1: LayerSpec = "Metal1drawing", layer_metal2: LayerSpec = "Metal2drawing", layer_esd: LayerSpec = "Recogesd", ) -> 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. layer_pwell: P-well layer. layer_activ: Active region layer. layer_gatpoly: Gate polysilicon layer. layer_nsd: N+ source/drain doping layer. layer_cont: Contact layer. layer_metal1: Metal1 layer. layer_metal2: Metal2 layer. layer_esd: ESD marker layer. 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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_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=layer_metal2, port_type="electrical", ) c.add_port( name="GND", center=(0, -gate_width / 2), width=nf * finger_pitch, orientation=270, layer=layer_metal1, port_type="electrical", ) # Add VLSIR metadata c.info["vlsir"] = { "model": model, "spice_type": "SUBCKT", "spice_lib": "sg13g2_moslv_mod.lib", "port_order": ["VDD", "VSS"], "port_map": {}, "params": {"w": width * 1e-6, "l": length * 1e-6, "ng": nf}, } return c
[docs] @gf.cell def ptap1( width: float = 1.0, length: float = 1.0, rows: int = 1, cols: int = 1, layer_activ: LayerSpec = "Activdrawing", layer_psd: LayerSpec = "pSDdrawing", layer_cont: LayerSpec = "Contdrawing", layer_metal1: LayerSpec = "Metal1drawing", ) -> 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. layer_activ: Active region layer. layer_psd: P+ source/drain doping layer. layer_cont: Contact layer. layer_metal1: Metal1 layer. 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=layer_activ, centered=True, ) c.add_ref(active) # P+ implant psd = gf.components.rectangle( size=(length + 2 * tap_enc, width + 2 * tap_enc), layer=layer_psd, centered=True, ) c.add_ref(psd) # 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=layer_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=layer_metal1, centered=True, ) c.add_ref(metal) # Add port c.add_port( name="TAP", center=(0, 0), width=width, orientation=0, layer=layer_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 # Add VLSIR metadata c.info["vlsir"] = { "model": "ptap1", "spice_type": "SUBCKT", "spice_lib": "resistors_mod.lib", "port_order": ["1", "2"], "port_map": {}, "params": { "w": width * 1e-6, "l": length * 1e-6, }, # TODO: Translate "rows, cols" } return c
[docs] @gf.cell def ntap1( width: float = 1.0, length: float = 1.0, rows: int = 1, cols: int = 1, layer_nwell: LayerSpec = "NWelldrawing", layer_activ: LayerSpec = "Activdrawing", layer_nsd: LayerSpec = "nSDdrawing", layer_cont: LayerSpec = "Contdrawing", layer_metal1: LayerSpec = "Metal1drawing", ) -> 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. layer_nwell: N-well layer. layer_activ: Active region layer. layer_nsd: N+ source/drain doping layer. layer_cont: Contact layer. layer_metal1: Metal1 layer. 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=layer_nwell, centered=True, ) c.add_ref(nwell) # N+ active region active = gf.components.rectangle( size=(length, width), layer=layer_activ, centered=True, ) c.add_ref(active) # N+ implant nsd = gf.components.rectangle( size=(length + 2 * tap_enc, width + 2 * tap_enc), layer=layer_nsd, centered=True, ) c.add_ref(nsd) # 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=layer_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=layer_metal1, centered=True, ) c.add_ref(metal) # Add port c.add_port( name="TAP", center=(0, 0), width=width, orientation=0, layer=layer_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 # Add VLSIR metadata c.info["vlsir"] = { "model": "ntap1", "spice_type": "SUBCKT", "spice_lib": "resistors_mod.lib", "port_order": ["1", "2"], "port_map": {}, "params": { "w": width * 1e-6, "l": length * 1e-6, }, # TODO: Translate "rows, cols" } return c
[docs] @gf.cell def sealring( width: float = 200.0, height: float = 200.0, ring_width: float = 5.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", layer_sealring: LayerSpec = "EdgeSealdrawing", ) -> 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. 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. layer_sealring: Seal ring marker layer. Returns: Component with seal ring layout. """ c = Component() # Create seal ring on all metal layers metal_layers = [ layer_metal1, layer_metal2, layer_metal3, layer_metal4, layer_metal5, layer_topmetal1, layer_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 = [ layer_via1, layer_via2, layer_via3, layer_via4, layer_topvia1, layer_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=layer_sealring, centered=True, ) seal_inner = gf.components.rectangle( size=(width - 1.0, height - 1.0), layer=layer_sealring, centered=True, ) seal_ring_mark = gf.boolean(seal_mark, seal_inner, "A-B", layer=layer_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
[docs] @gf.cell def guard_ring( width: float = 0.5, guardRingSpacing: float = 0.14, guardRingType: Literal["psub", "nwell"] = "psub", bbox: tuple[Point, Point] | None = None, path: list[tuple[Point, Point]] | None = None, layer_activ: LayerSpec = "Activdrawing", layer_cont: LayerSpec = "Contdrawing", layer_metal1: LayerSpec = "Metal1drawing", layer_psd: LayerSpec = "pSDdrawing", layer_nwell: LayerSpec = "NWelldrawing", layer_nsd: LayerSpec = "nSDdrawing", **kwargs, ) -> Component: """ Create an N-Well (NW) and N-Plus (NP) or a P-Plus (PP) guard ring around a boundary box, or, if `bbox` is not provided, along a provided `path` of points. Args: width: Width of the guard ring group path, defining the width of the Metal1 Path. guardRingSpacing: Spacing between the Metal1 Path and the component BBox. guardRingType: Literal["psub", 'nwell'] Type of Guard-Ring (NP = nwell or PP = psub). bbox: Encapsulated component bounding box. path: Path point for the group path defining the Guard Ring, layer_activ: Activ drawing layer. layer_cont: Contact Via drawing layer. layer_metal1: Metal1 drawing layer. layer_psd: pSD / P-Plus drawing layer. layer_nwell: NWell drawing layer. layer_nsd: nSD / N-Plus drawing layer. Returns: c - Component containing the Guard-Ring group path. Raises: """ gr_drc = { # metals "m1_min_width": tech.TECH.metal1_width, # active regions and contact "cont_min_size": tech.TECH.cont_size, "cont_min_spacing": tech.TECH.cont_spacing, "cont_min_enclose_active": tech.TECH.cont_enc_active, "cont_min_enclose_metal": tech.TECH.cont_enc_metal, # TODO: add in the original tech struct "active_min_enclose_np": 0.14, "active_min_enclose_pp": 0.14, "np_min_enclose_nw": 0.14, } min_width = gr_drc["cont_min_size"] + 2 * max( gr_drc["cont_min_enclose_active"], gr_drc["cont_min_enclose_metal"] ) min_width = max(min_width, gr_drc["m1_min_width"]) assert width >= min_width, ( f"Guard Ring width >= {min_width} to comply with Min cont enclosure and metal width" ) # define nrows and ncols of the required tap nrows = int(floor(width / min_width)) c = Component() # define the path if bbox is not None: path = [ ( bbox[0][0] - guardRingSpacing - width / 2, bbox[1][1] + guardRingSpacing + width / 2, ), ( bbox[1][0] + guardRingSpacing + width / 2, bbox[1][1] + guardRingSpacing + width / 2, ), ( bbox[1][0] + guardRingSpacing + width / 2, bbox[0][1] - guardRingSpacing - width / 2, ), ( bbox[0][0] - guardRingSpacing - width / 2, bbox[0][1] - guardRingSpacing - width / 2, ), ( bbox[0][0] - guardRingSpacing - width / 2, bbox[1][1] + guardRingSpacing + width, ), ] enclosure = max( gr_drc["cont_min_enclose_active"], gr_drc["cont_min_enclose_metal"] ) cont_path = [ ( bbox[0][0] - guardRingSpacing - enclosure, bbox[1][1] + guardRingSpacing + enclosure, ), ( bbox[1][0] + guardRingSpacing + enclosure, bbox[1][1] + guardRingSpacing + enclosure, ), ( bbox[1][0] + guardRingSpacing + enclosure, bbox[0][1] - guardRingSpacing - enclosure, ), ( bbox[0][0] - guardRingSpacing - enclosure, bbox[0][1] - guardRingSpacing - enclosure, ), (bbox[0][0] - guardRingSpacing - enclosure, bbox[1][1] + guardRingSpacing), ] assert path is not None, "Neither path or bbox was provided." # place taps around path tap_layers = [layer_activ, layer_metal1] main = None for layer_spec in tap_layers: p = gf.path.extrude(gf.path.Path(path), width=width, layer=layer_spec) main = c.add_ref(p) if guardRingType == "psub": sep = gr_drc["active_min_enclose_pp"] last_point = list(path[-1]) last_edge = (path[-1][0] - path[-2][0], path[-1][1] - path[-2][1]) norm = np.linalg.norm(last_edge) dir_vec = np.array(last_edge) / norm # manhattan dir_vec[0] = round(dir_vec[0]) dir_vec[1] = round(dir_vec[1]) last_point[0] += sep * dir_vec[0] last_point[1] += sep * dir_vec[1] new_path = path.copy() new_path[-1] = tuple(last_point) p = gf.path.extrude( gf.path.Path(new_path), width=width + 2 * sep, layer=layer_psd ) c.add_ref(p) if guardRingType == "nwell": sep = gr_drc["active_min_enclose_np"] last_point = list(path[-1]) last_edge = (path[-1][0] - path[-2][0], path[-1][1] - path[-2][1]) norm = np.linalg.norm(last_edge) dir_vec = np.array(last_edge) / norm # manhattan dir_vec[0] = round(dir_vec[0]) dir_vec[1] = round(dir_vec[1]) last_point[0] += sep * dir_vec[0] last_point[1] += sep * dir_vec[1] new_path = path.copy() new_path[-1] = tuple(last_point) p = gf.path.extrude( gf.path.Path(new_path), width=width + 2 * sep, layer=layer_nsd ) sep += gr_drc["np_min_enclose_nw"] last_point = list(path[-1]) last_point[0] += sep * dir_vec[0] last_point[1] += sep * dir_vec[1] new_path = path.copy() new_path[-1] = tuple(last_point) nwl = gf.path.extrude( gf.path.Path(new_path), width=width + 2 * sep, layer=layer_nwell ) c.add_ref(p) c.add_ref(nwl) cont_tap = cells.via_array( via_type=layer_cont.split("drawing")[0], via_size=gr_drc["cont_min_size"], via_spacing=gr_drc["cont_min_size"] + gr_drc["cont_min_spacing"], via_enclosure=gr_drc["cont_min_enclose_active"], columns=1, rows=nrows, ) conts = gf.path.along_path( gf.path.Path(cont_path if bbox is not None else path), cont_tap, gr_drc["cont_min_spacing"] + gr_drc["cont_min_size"], 0.0, ) cont_ref = c.add_ref(conts) cont_ref.x = main.x cont_ref.y = main.y c.info["model"] = f"{guardRingType}-guard-ring" c.info["width"] = width c.info["rows"] = nrows c.info["guardRingSpacing"] = guardRingSpacing return c
if __name__ == "__main__": from gdsfactory.difftest import xor from ihp import PDK from ihp.cells import fixed PDK.activate() # Test the components # c0 = cells.svaricap() # original # c1 = svaricap() # New # # c = gf.grid([c0, c1], spacing=100) # c = xor(c0, c1) # c.show() # c0 = cells.esd_nmos() # original # c1 = esd_nmos() # New # c = xor(c0, c1) # c.show() c0 = fixed.ptap1() # original c1 = ptap1() # New c = xor(c0, c1) c.show() # c0 = fixed.sealring() # original # c1 = sealring() # New # c = xor(c0, c1) # c.show()