"""Passive components (varicaps, ESD, taps, seal rings) for IHP PDK."""
from typing import Literal
import gdsfactory as gf
from .. import tech
from .ihp_pycell import CbTapCalc, eng_string_to_float
from .ihp_pycell import esd as esdIHP
from .ihp_pycell import ntap1 as ntap1IHP
from .ihp_pycell import ptap1 as ptap1IHP
from .ihp_pycell import sealring as sealringIHP
from .utils import *
[docs]
@gf.cell
def esd(
model: Literal[
"diodevdd_2kv",
"diodevss_2kv",
"diodevdd_4kv",
"diodevss_4kv",
"nmoscl_2",
"nmoscl_4",
] = "diodevdd_2kv",
) -> gf.Component:
"""Create an ESD protection device layout.
This function generates an electrostatic discharge (ESD) protection device
using the specified model. Available models include diodes and NMOS clamps
for different voltage ratings.
Args:
model: Device model name. Options:
- 'diodevdd_2kv': Diode from VDD for 2 kV rating.
- 'diodevss_2kv': Diode to VSS for 2 kV rating.
- 'diodevdd_4kv': Diode from VDD for 4 kV rating.
- 'diodevss_4kv': Diode to VSS for 4 kV rating.
- 'nmoscl_2': NMOS clamp for 2 kV rating.
- 'nmoscl_4': NMOS clamp for 4 kV rating.
Returns:
gdsfactory.Component: The generated ESD protection layout.
"""
params = {
"cdf_version": tech.techParams["CDFVersion"],
"Display": "Selected",
"model": model,
}
c = generate_gf_from_ihp(
cell_name="esd", cell_params=params, function_name=esdIHP()
)
## add ports to the component
# default direction should be away from the device center
# set port names and orientations based on model
if model == "diodevdd_2kv":
gf.add_ports.add_ports_from_boxes(
c,
pin_layer=(tech.LAYER.Metal1pin),
port_type="electrical",
ports_on_short_side=True,
)
c.ports["e1"].orientation = 270
c.ports["e1"].name = "VSS"
gf.add_ports.add_ports_from_boxes(
c,
pin_layer=(tech.LAYER.Metal2pin),
port_type="electrical",
ports_on_short_side=True,
auto_rename_ports=False,
)
c.ports["e1"].orientation = 180
c.ports["e1"].name = "PAD"
c.ports["e2"].orientation = 0
c.ports["e2"].name = "VDD"
elif model == "diodevdd_4kv":
gf.add_ports.add_ports_from_boxes(
c,
pin_layer=(tech.LAYER.Metal1pin),
port_type="electrical",
ports_on_short_side=True,
)
c.ports["e1"].orientation = 270
c.ports["e1"].name = "VSS"
gf.add_ports.add_ports_from_boxes(
c,
pin_layer=(tech.LAYER.Metal2pin),
port_type="electrical",
ports_on_short_side=True,
auto_rename_ports=False,
)
c.ports["e1"].orientation = 0
c.ports["e1"].name = "VDD"
c.ports["e2"].orientation = 180
c.ports["e2"].name = "PAD"
elif model in ["diodevss_2kv", "diodevss_4kv"]:
gf.add_ports.add_ports_from_boxes(
c,
pin_layer=(tech.LAYER.Metal1pin),
port_type="electrical",
ports_on_short_side=True,
)
c.ports["e1"].orientation = 90
c.ports["e1"].name = "VDD"
gf.add_ports.add_ports_from_boxes(
c,
pin_layer=(tech.LAYER.Metal2pin),
port_type="electrical",
ports_on_short_side=True,
auto_rename_ports=False,
)
c.ports["e1"].orientation = 180
c.ports["e1"].name = "PAD"
c.ports["e2"].orientation = 0
c.ports["e2"].name = "VSS"
else: # NMOS clamp
gf.add_ports.add_ports_from_boxes(
c,
pin_layer=(tech.LAYER.Metal3pin),
port_type="electrical",
ports_on_short_side=True,
)
c.ports["e1"].orientation = 270
c.ports["e1"].name = "VSS"
c.ports["e2"].orientation = 90
c.ports["e2"].name = "VDD"
return c
[docs]
@gf.cell
def ptap1(
width: float = 0.78,
length: float = 0.78,
) -> gf.Component:
"""Create a P+ substrate tap layout.
This function generates a parametric P+ substrate tap with configurable
width and length. It is typically used for connecting the P-substrate
to a reference potential.
Args:
width: Width of the tap in micrometers.
length: Length of the tap in micrometers.
Returns:
gdsfactory.Component: The generated P+ substrate tap layout.
"""
area = width * length
perimeter = 2 * (width + length)
params = {
"cdf_version": tech.techParams["CDFVersion"],
"Display": "Selected",
"Calculate": "R,A",
"R": CbTapCalc(
"R", 0, length * 1e-6, width * 1e-6, "ptap1"
), # TODO Is this used?
"w": width * 1e-6, # Length in μm
"l": length * 1e-6, # Length in μm
"A": area,
"Perim": perimeter,
"Rspec": 0.980 * 1e-9, # hardcoded in the PCell
"Wmin": eng_string_to_float(tech.techParams["ptap1_minLW"]),
"Lmin": eng_string_to_float(tech.techParams["ptap1_minLW"]),
"m": 1,
}
c = generate_gf_from_ihp(
cell_name="ptap1", cell_params=params, function_name=ptap1IHP()
)
# add ports to the component
gf.add_ports.add_ports_from_boxes(
c,
pin_layer=(tech.LAYER.Metal1pin),
port_type="electrical",
ports_on_short_side=True,
)
return c
[docs]
@gf.cell
def ntap1(
width=0.78,
length=0.78,
) -> gf.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.
"""
area = width * length
perimeter = 2 * (width + length)
params = {
"cdf_version": tech.techParams["CDFVersion"],
"Display": "Selected",
"Calculate": "R,A",
"R": CbTapCalc(
"R", 0, length * 1e-6, width * 1e-6, "ntap1"
), # TODO Is this used?
"w": width * 1e-6, # Length in μm
"l": length * 1e-6, # Length in μm
"A": area,
"Perim": perimeter,
"Rspec": 0.980 * 1e-9, # hardcoded in the PCell
"Wmin": eng_string_to_float(tech.techParams["ntap1_minLW"]),
"Lmin": eng_string_to_float(tech.techParams["ntap1_minLW"]),
"m": 1,
}
c = generate_gf_from_ihp(
cell_name="ntap1", cell_params=params, function_name=ntap1IHP()
)
# add ports to the component
gf.add_ports.add_ports_from_boxes(
c,
pin_layer=(tech.LAYER.Metal1pin),
port_type="electrical",
ports_on_short_side=True,
)
return c
[docs]
@gf.cell
def sealring(
width: float = 400.0,
height: float = 400.0,
addLabel: Literal["nil", "t"] = "nil",
addSlit: Literal["nil", "t"] = "nil",
edgeBox: float = 25.0,
) -> gf.Component:
"""Create a seal ring for die protection.
This function generates a parametric seal ring around the die with optional
label and slit features. The seal ring helps protect the chip from mechanical
stress and contamination.
Args:
width: Inner width of the seal ring in micrometers.
height: Inner height of the seal ring in micrometers.
addLabel: Include label on the seal ring. Options: 'nil' (no label), 't' (add label).
addSlit: Include slit in the seal ring. Options: 'nil' (no slit), 't' (add slit).
edgeBox: Distance from die edge to the seal ring in micrometers.
Returns:
gdsfactory.Component: The generated seal ring layout.
"""
params = {
"cdf_version": tech.techParams["CDFVersion"],
"Display": "Selected",
"l": width * 1e-6, # Length in μm
"w": height * 1e-6, # Length in μm
"addLabel": addLabel,
"addSlit": addSlit,
"Wmin": eng_string_to_float(tech.techParams["sealring_complete_minW"]),
"Lmin": eng_string_to_float(tech.techParams["sealring_complete_minL"]),
"edgeBox": edgeBox * 1e-6,
}
c = generate_gf_from_ihp(
cell_name="sealring", cell_params=params, function_name=sealringIHP()
)
# add ports to the component
# ports should be added manually if needed
return c
if __name__ == "__main__":
# Test the components
c2 = esd(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()