Source code for ihp.cells.antennas

"""Äntenna components for IHP PDK."""

import math
from typing import Literal

import gdsfactory as gf
from gdsfactory.typings import LayerSpec
from kfactory.schematic import DSchematic

from ihp.tech import TECH as _TECH

_XS = "metal1_routing"


def fix(value: float) -> int:
    """Rounds a floating-point value down to the nearest integer.

    Args:
        value: Value to be rounded or passed through.

    Returns:
        The rounded integer of ``value``.
    """
    return int(math.floor(value))


def GridFix(x: float) -> float:
    """Snaps a coordinate to the technology grid.

    Args:
        x: Coordinate value to be aligned to the technology grid.

    Returns:
        The grid-aligned coordinate value.
    """
    SG13_GRID = _TECH.grid  # tech["grid"]
    SG13_IGRID = 1 / SG13_GRID
    SG13_EPSILON = _TECH.epsilon  # tech["epsilon1"]
    return (
        fix(x * SG13_IGRID + SG13_EPSILON) * SG13_GRID
    )  # always use "nice" numbers, as 1/grid may be irrational


def DrawContArray(
    c,
    cont_layer,
    y_min,
    x_min,
    width,
    length,
    cont_size,
    cont_dist,
    cont_diff_over,
) -> tuple[float, float, float, float]:
    """Distributes an array of square contacts inside a rectangular region.

    Args:
        c: Target gdsfactory component to which the contacts are added.
        cont_layer: Layer on which the contact rectangles are drawn.
        y_min: Y-coordinate of the lower-left corner of the enclosing region.
        x_min: X-coordinate of the lower-left corner of the enclosing region.
        width: Width of the enclosing region.
        length: Length of the enclosing region.
        cont_size: Size of one square contact.
        cont_dist: Spacing between adjacent contacts.
        cont_diff_over: Minimum enclosure of contacts by the surrounding
            diffusion or active region.

    Returns:
        Tuple[float, float, float, float]: Coordinates of the bounding box
        enclosing the generated contact array in the form
        ``(x_min, y_min, x_max, y_max)``.
    """
    epsilon = _TECH.epsilon

    xanz = fix(
        (width - 2 * cont_diff_over + cont_dist + epsilon) / (cont_size + cont_dist)
    )
    yanz = fix(
        (length - 2 * cont_diff_over + cont_dist + epsilon) / (cont_size + cont_dist)
    )

    cont_dist_big = _TECH.cont_b1
    cont_dist_big_nr = _TECH.cont_b1_nr
    # now check, if it is cont and more than 4 rows/lines
    if cont_layer == "Cont" and xanz >= cont_dist_big_nr and yanz >= cont_dist_big_nr:
        # it has to be bigger space between contacts
        cont_dist = cont_dist_big
        # it has to be bigger space between contacts
        xanz = fix(
            (width - 2 * cont_diff_over + cont_dist + epsilon) / (cont_size + cont_dist)
        )
        yanz = fix(
            (length - 2 * cont_diff_over + cont_dist + epsilon)
            / (cont_size + cont_dist)
        )

    xmin = xanz * (cont_size + cont_dist) - cont_dist + 2 * cont_diff_over
    ymin = yanz * (cont_size + cont_dist) - cont_dist + 2 * cont_diff_over
    xoff = (width - xmin) / 2
    xoff = GridFix(xoff)
    yoff = (length - ymin) / 2
    yoff = GridFix(yoff)

    for j in range(int(yanz)):
        for i in range(int(xanz)):
            cont = c << gf.components.rectangle(
                size=(cont_size, cont_size), layer=cont_layer
            )

            cont.move(
                (
                    x_min + xoff + cont_diff_over + (cont_size + cont_dist) * i,
                    y_min + yoff + cont_diff_over + (cont_size + cont_dist) * j,
                )
            )

    x_min = x_min + xoff + cont_diff_over
    y_min = y_min + yoff + cont_diff_over
    x_max = x_min + (cont_size + cont_dist) * i + cont_size
    y_max = y_min + (cont_size + cont_dist) * j + cont_size

    return x_min, y_min, x_max, y_max


def dantenna_schematic(
    width: float = 0.78,
    length: float = 0.78,
    addRecLayer: Literal["t", "f"] = "t",
    guardRingType: Literal["none", "psub"] = "none",
    guardRingDistance: float = 1.0,
) -> DSchematic:
    s = DSchematic()
    s.info["tags"] = ["IHP", "diode", "antenna"]
    s.info["symbol"] = "diode"
    s.info["ports"] = [
        {"name": "1", "side": "top", "type": "electric"},
        {"name": "2", "side": "bottom", "type": "electric"},
    ]
    s.info["models"] = [
        {
            "language": "spice",
            "name": "dantenna",
            "spice_type": "SUBCKT",
            "library": "ihp/models/ngspice/models/cornerDIO.lib",
            "sections": ["dio_tt", "dio_ss", "dio_ff"],
            "port_order": ["1", "2"],
            "params": {"w": "width * 1e-6", "l": "length * 1e-6"},
        }
    ]
    s.create_port(name="1", cross_section=_XS, x=0, y=1, orientation=90)
    s.create_port(name="2", cross_section=_XS, x=0, y=-1, orientation=270)
    return s


[docs] @gf.cell(schematic_function=dantenna_schematic) def dantenna( width: float = 0.78, length: float = 0.78, addRecLayer: Literal["t", "f"] = "t", guardRingType: Literal["none", "psub"] = "none", guardRingDistance: float = 1.0, ) -> gf.Component: """Creates a diode antenna (dantenna) structure. This function generates a layout cell containing a rectangular antenna region with optional recognition layers and guard ring structures. Parameters allow customization of the antenna geometry and the type and spacing of guard rings. Args: width: Width of the antenna rectangle in microns. length: Length of the antenna rectangle in microns. addRecLayer: Recognition layer to add (e.g., 'M1' for metal1, 'M2' for metal2, or None for none). guardRingType: Type of guard ring to include. Options include: - 'none': No guard ring - 'psub': P-type guard ring guardRingDistance: Spacing between the antenna body and guard ring in microns. Returns: gdsfactory.Component: The generated antenna component. Raises: ValueError: If width or length is outside allowed range. """ if width < _TECH.dantenna_min_width or width > _TECH.dantenna_max_width: raise ValueError( f"dantenna width={width} out of range [{_TECH.dantenna_min_width}, {_TECH.dantenna_max_width}]" ) if length < _TECH.dantenna_min_length or length > _TECH.dantenna_max_length: raise ValueError( f"dantenna length={length} out of range [{_TECH.dantenna_min_length}, {_TECH.dantenna_max_length}]" ) c = gf.Component() layer_metal1: LayerSpec = "Metal1drawing" ndiff_layer: LayerSpec = "Activdrawing" pdiff_layer: LayerSpec = "Activdrawing" pdiffx_layer: LayerSpec = "pSDdrawing" cont_layer: LayerSpec = "Contdrawing" diods_layer: LayerSpec = "Recogdiode" layer_text: LayerSpec = "TEXTdrawing" cont_size = _TECH.cont_size cont_dist = _TECH.cont_spacing cont_diff_over = _TECH.cont_enc_active pdiffx_over = _TECH.pSD_a diods_over = _TECH.dantenna_dov typ = "N" x_min, y_min, x_max, y_max = DrawContArray( c, cont_layer, 0, 0, width, length, cont_size, cont_dist, cont_diff_over, ) # Metal1 encloses the contacts metal1_ref = c << gf.components.rectangle( size=(x_max - x_min, y_max - y_min), layer=layer_metal1 ) metal1_ref.move((x_min, y_min)) if typ == "N": c.add_ref(gf.components.rectangle(size=(width, length), layer=ndiff_layer)) else: c.add_ref(gf.components.rectangle(size=(width, length), layer=pdiff_layer)) c.add_ref( gf.components.rectangle( size=(width + 2 * pdiffx_over, length + 2 * pdiffx_over), layer=pdiffx_layer, ) ).move((-pdiffx_over, -pdiffx_over)) c.add_label( "dant", layer=layer_text, position=( width / 2, length / 2, ), ) if addRecLayer == "t": c.add_ref( gf.components.rectangle( size=(width + 2 * diods_over, length + 2 * diods_over), layer=diods_layer, ) ).move((-diods_over, -diods_over)) return c
def dpantenna_schematic( width: float = 0.78, length: float = 0.78, addRecLayer: Literal["t", "f"] = "t", guardRingType: Literal["none", "nwell"] = "none", guardRingDistance: float = 1.0, ) -> DSchematic: s = DSchematic() s.info["tags"] = ["IHP", "diode", "antenna"] s.info["symbol"] = "diode" s.info["ports"] = [ {"name": "1", "side": "top", "type": "electric"}, {"name": "2", "side": "bottom", "type": "electric"}, ] s.info["models"] = [ { "language": "spice", "name": "dpantenna", "spice_type": "SUBCKT", "library": "ihp/models/ngspice/models/cornerDIO.lib", "sections": ["dio_tt", "dio_ss", "dio_ff"], "port_order": ["1", "2"], "params": {"w": "width * 1e-6", "l": "length * 1e-6"}, } ] s.create_port(name="1", cross_section=_XS, x=0, y=1, orientation=90) s.create_port(name="2", cross_section=_XS, x=0, y=-1, orientation=270) return s
[docs] @gf.cell(schematic_function=dpantenna_schematic) def dpantenna( width: float = 0.78, length: float = 0.78, addRecLayer: Literal["t", "f"] = "t", guardRingType: Literal["none", "nwell"] = "none", guardRingDistance: float = 1.0, ) -> gf.Component: """Creates a dual-polarity antenna (dpantenna) structure. Generates a layout cell containing a rectangular antenna region with an optional recognition layer and an optional n-well guard ring. Parameters allow customization of the antenna geometry and the spacing between the antenna body and the surrounding guard ring. Args: width: Width of the antenna rectangle in microns. length: Length of the antenna rectangle in microns. addRecLayer: Whether to add a recognition layer. Valid values: - 't': Add recognition layer. - 'f': Do not add a recognition layer. guardRingType: Type of guard ring to include. Valid values: - 'none': No guard ring. - 'nwell': Surrounding n-well guard ring. guardRingDistance: Spacing between the antenna body and the n-well guard ring, in microns. Returns: gdsfactory.Component: The generated antenna component. Raises: ValueError: If width or length is outside allowed range. """ if width < _TECH.dpantenna_min_width or width > _TECH.dpantenna_max_width: raise ValueError( f"dpantenna width={width} out of range [{_TECH.dpantenna_min_width}, {_TECH.dpantenna_max_width}]" ) if length < _TECH.dpantenna_min_length or length > _TECH.dpantenna_max_length: raise ValueError( f"dpantenna length={length} out of range [{_TECH.dpantenna_min_length}, {_TECH.dpantenna_max_length}]" ) c = gf.Component() layer_metal1: LayerSpec = "Metal1drawing" pdiff_layer: LayerSpec = "Activdrawing" pdiffx_layer: LayerSpec = "pSDdrawing" cont_layer: LayerSpec = "Contdrawing" diods_layer: LayerSpec = "Recogdiode" layer_text: LayerSpec = "TEXTdrawing" layer_nwell: LayerSpec = "NWelldrawing" cont_size = _TECH.cont_size cont_dist = _TECH.cont_spacing cont_diff_over = _TECH.cont_enc_active pdiffx_over = _TECH.psd_activ_over diods_over = _TECH.dpantenna_dov NW_c = _TECH.nw_activ_over_lv x_min, y_min, x_max, y_max = DrawContArray( c, cont_layer, 0, 0, width, length, cont_size, cont_dist, cont_diff_over, ) # Metal1 encloses the contacts metal1_ref = c << gf.components.rectangle( size=(x_max - x_min, y_max - y_min), layer=layer_metal1 ) metal1_ref.move((x_min, y_min)) c.add_ref(gf.components.rectangle(size=(width, length), layer=pdiff_layer)) c.add_ref( gf.components.rectangle( size=(width + 2 * pdiffx_over, length + 2 * pdiffx_over), layer=pdiffx_layer, ) ).move((-pdiffx_over, -pdiffx_over)) c.add_label( "dant", layer=layer_text, position=( width / 2, length / 2, ), ) if addRecLayer == "t": c.add_ref( gf.components.rectangle( size=(width + 2 * diods_over, length + 2 * diods_over), layer=diods_layer, ) ).move((-diods_over, -diods_over)) c.add_ref( gf.components.rectangle( size=(width + 2 * NW_c, length + 2 * NW_c), layer=layer_nwell, ) ).move((-NW_c, -NW_c)) return c