"""Äntenna components for IHP PDK."""
import math
from typing import Literal
import gdsfactory as gf
from gdsfactory.typings import LayerSpec
from ihp.tech import TECH as _TECH
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
[docs]
@gf.cell
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))
# VLSIR Simulation Metadata
c.info["vlsir"] = {
"model": "dantenna",
"spice_type": "SUBCKT",
"spice_lib": "diodes.lib",
"port_order": ["1", "2"],
"port_map": {}, # No physical ports defined on component
"params": {"w": width * 1e-6, "l": length * 1e-6},
}
return c
[docs]
@gf.cell
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))
# VLSIR Simulation Metadata
c.info["vlsir"] = {
"model": "dpantenna",
"spice_type": "SUBCKT",
"spice_lib": "diodes.lib",
"port_order": ["1", "2"],
"port_map": {}, # No physical ports defined on component
"params": {"w": width * 1e-6, "l": length * 1e-6},
}
return c
if __name__ == "__main__":
from gdsfactory.difftest import xor
from ihp import PDK, cells2
PDK.activate()
c1 = dpantenna()
c0 = cells2.dpantenna(guardRingType="nwell")
c = xor(c0, c1)
c.show()