Source code for sky130.pcells.resistors

"""Magic-parity resistor generators for sky130.

Provides poly and diffusion resistor parametric cells that match the geometry
produced by Magic's sky130 device generators, including guard rings, contact
heads, NPC, implant layers, and metal interconnect.

All geometry is centered at the origin to match Magic's output coordinate system.
"""

from __future__ import annotations

from math import floor

import gdsfactory as gf

from sky130.layers import LAYER

# ---------------------------------------------------------------------------
# Shared helpers (same pattern as mosfets.py)
# ---------------------------------------------------------------------------


def _snap(val: float, grid: float = 0.005) -> float:
    """Snap a value to the nearest grid point (default 5 nm)."""
    return round(val / grid) * grid


def _rect(c: gf.Component, layer, x0: float, y0: float, x1: float, y1: float):
    """Add a rectangle defined by two corners ``(x0, y0)`` to ``(x1, y1)``."""
    x0, y0, x1, y1 = _snap(x0), _snap(y0), _snap(x1), _snap(y1)
    c.add_polygon(
        [(x0, y0), (x1, y0), (x1, y1), (x0, y1)],
        layer=layer,
    )


# ---------------------------------------------------------------------------
# Guard-ring builder (inlined to give us full control over licon placement)
# ---------------------------------------------------------------------------

_LICON = 0.17  # licon contact size
_LICON_PITCH = 0.34  # licon pitch (size + spacing)
_GR_RING_W = 0.17  # guard-ring tap width
_PSDM_ENC = 0.125  # psdm enclosure of tap


def _guard_ring_licons_side(
    c: gf.Component,
    bar_x0: float,
    bar_x1: float,
    bar_y0: float,
    bar_y1: float,
) -> None:
    """Place licon contacts along a vertical guard-ring bar, centred at y=0.

    The contacts are placed at 0.34 um pitch, centred within the bar.
    Magic leaves a clearance of at least 0.310 um from each bar end to the
    outermost contact, matching the guard-ring contact placement algorithm.
    """
    bar_h = _snap(bar_y1 - bar_y0)
    cx = _snap((bar_x0 + bar_x1) / 2 - _LICON / 2)

    # Effective available height: bar_h minus clearance at each end
    eff_h = _snap(bar_h - 2 * 0.310)
    n = max(0, 1 + floor((eff_h - _LICON) / _LICON_PITCH))
    if n < 1:
        return

    array_h = _snap((n - 1) * _LICON_PITCH + _LICON)
    y_start = _snap((bar_y0 + bar_y1) / 2 - array_h / 2)

    for i in range(n):
        y = _snap(y_start + i * _LICON_PITCH)
        _rect(c, LAYER.licon1drawing, cx, y, cx + _LICON, y + _LICON)


def _guard_ring_licons_horiz(
    c: gf.Component,
    bar_x0: float,
    bar_x1: float,
    bar_y0: float,
    bar_y1: float,
    fit_width: float,
) -> None:
    """Place licon contacts along a horizontal guard-ring bar.

    ``fit_width`` controls the available width for contacts (typically the
    inner width of the guard ring).  Contacts are centred within the bar.
    """
    cy = _snap(bar_y0)  # bottom edge of the 0.17-high bar

    avail = fit_width
    nc = max(1, 1 + floor((avail - _LICON) / _LICON_PITCH))
    array_w = _snap((nc - 1) * _LICON_PITCH + _LICON)
    x_start = _snap((bar_x0 + bar_x1) / 2 - array_w / 2)

    for i in range(nc):
        x = _snap(x_start + i * _LICON_PITCH)
        _rect(c, LAYER.licon1drawing, x, cy, x + _LICON, cy + _LICON)


def _pwell_guard_ring(
    c: gf.Component,
    inner_w: float,
    inner_h: float,
    licon_fit_w: float | None = None,
) -> None:
    """Draw a p-well guard ring centred at the origin.

    ``inner_w`` and ``inner_h`` specify the full inner opening dimensions.
    ``licon_fit_w`` controls how many licons fit on the horizontal bars.
    The ring uses non-overlapping segments (sides fit between top/bottom bars).
    """
    rw = _GR_RING_W
    hw = inner_w / 2
    hh = inner_h / 2

    # Outer bounds
    ox = _snap(hw + rw)
    oy = _snap(hh + rw)

    # Tap segments (65, 44) ------------------------------------------------
    # Top & bottom bars span full outer width
    _rect(c, LAYER.tapdrawing, -ox, hh, ox, oy)  # top
    _rect(c, LAYER.tapdrawing, -ox, -oy, ox, -hh)  # bottom
    # Left & right bars span inner height only (no corner overlap)
    _rect(c, LAYER.tapdrawing, -ox, -hh, -hw, hh)  # left
    _rect(c, LAYER.tapdrawing, hw, -hh, ox, hh)  # right

    # Li1 (67, 20) — same footprint as tap --------------------------------
    _rect(c, LAYER.li1drawing, -ox, hh, ox, oy)
    _rect(c, LAYER.li1drawing, -ox, -oy, ox, -hh)
    _rect(c, LAYER.li1drawing, -ox, -hh, -hw, hh)
    _rect(c, LAYER.li1drawing, hw, -hh, ox, hh)

    # PSDM (94, 20) — 4-piece ring extending 0.125 beyond tap -------------
    pe = _PSDM_ENC
    _rect(c, LAYER.psdmdrawing, -ox - pe, hh - pe, ox + pe, oy + pe)  # top
    _rect(c, LAYER.psdmdrawing, -ox - pe, -oy - pe, ox + pe, -hh + pe)  # bottom
    _rect(c, LAYER.psdmdrawing, -ox - pe, -hh + pe, -hw + pe, hh - pe)  # left
    _rect(c, LAYER.psdmdrawing, hw - pe, -hh + pe, ox + pe, hh - pe)  # right

    # Licon contacts on guard ring -----------------------------------------
    fw = licon_fit_w if licon_fit_w is not None else inner_w
    # Horizontal bars — fit contacts based on device width
    _guard_ring_licons_horiz(c, -ox, ox, hh, oy, fit_width=fw)
    _guard_ring_licons_horiz(c, -ox, ox, -oy, -hh, fit_width=fw)

    # Vertical bars
    _guard_ring_licons_side(c, -ox, -hw, -hh, hh)
    _guard_ring_licons_side(c, hw, ox, -hh, hh)


def _pwell_guard_ring_solid_psdm(
    c: gf.Component,
    inner_w: float,
    inner_h: float,
    licon_fit_w: float | None = None,
) -> None:
    """Draw a p-well guard ring with a SOLID psdm rectangle (for high-R poly)."""
    rw = _GR_RING_W
    hw = inner_w / 2
    hh = inner_h / 2

    ox = _snap(hw + rw)
    oy = _snap(hh + rw)

    # Tap
    _rect(c, LAYER.tapdrawing, -ox, hh, ox, oy)
    _rect(c, LAYER.tapdrawing, -ox, -oy, ox, -hh)
    _rect(c, LAYER.tapdrawing, -ox, -hh, -hw, hh)
    _rect(c, LAYER.tapdrawing, hw, -hh, ox, hh)

    # Li1
    _rect(c, LAYER.li1drawing, -ox, hh, ox, oy)
    _rect(c, LAYER.li1drawing, -ox, -oy, ox, -hh)
    _rect(c, LAYER.li1drawing, -ox, -hh, -hw, hh)
    _rect(c, LAYER.li1drawing, hw, -hh, ox, hh)

    # PSDM — single solid rectangle
    pe = _PSDM_ENC
    _rect(c, LAYER.psdmdrawing, -ox - pe, -oy - pe, ox + pe, oy + pe)

    # Licon contacts
    fw = licon_fit_w if licon_fit_w is not None else inner_w
    _guard_ring_licons_horiz(c, -ox, ox, hh, oy, fit_width=fw)
    _guard_ring_licons_horiz(c, -ox, ox, -oy, -hh, fit_width=fw)
    _guard_ring_licons_side(c, -ox, -hw, -hh, hh)
    _guard_ring_licons_side(c, hw, ox, -hh, hh)


# ---------------------------------------------------------------------------
# Contact arrays (mcon)
# ---------------------------------------------------------------------------


def _mcon_array(
    c: gf.Component,
    x0: float,
    y0: float,
    x1: float,
    y1: float,
) -> None:
    """Place mcon contacts (0.17 x 0.17) in met1 area (x0,y0)-(x1,y1).

    Uses enclosure (0.030, 0.065) matching Magic's mcon placement in resistors.
    Contacts are centred in x and aligned to y0 + enc_y in y.
    """
    size = 0.17
    pitch_x = 0.36  # 0.17 + 0.19
    pitch_y = 0.36
    enc_x = 0.030
    enc_y = 0.065

    avail_w = _snap(x1 - x0 - 2 * enc_x)
    avail_h = _snap(y1 - y0 - 2 * enc_y)

    if avail_w < size or avail_h < size:
        return

    nc = max(1, 1 + floor((avail_w - size) / pitch_x))
    nr = max(1, 1 + floor((avail_h - size) / pitch_y))

    array_w = _snap((nc - 1) * pitch_x + size)

    ox = _snap((x0 + x1) / 2 - array_w / 2)
    oy = _snap(y0 + enc_y)

    for r in range(nr):
        for col in range(nc):
            x = _snap(ox + col * pitch_x)
            y = _snap(oy + r * pitch_y)
            _rect(c, LAYER.mcondrawing, x, y, x + size, y + size)


def _nd_mcon_array(
    c: gf.Component,
    x0: float,
    y0: float,
    x1: float,
    y1: float,
) -> None:
    """Place mcon contacts for ND resistor head/tail (different enc from poly)."""
    size = 0.17
    pitch_x = 0.36
    pitch_y = 0.36
    enc_x = 0.060
    enc_y = 0.060

    avail_w = _snap(x1 - x0 - 2 * enc_x)
    avail_h = _snap(y1 - y0 - 2 * enc_y)

    if avail_w < size or avail_h < size:
        return

    nc = max(1, 1 + floor((avail_w - size) / pitch_x))
    nr = max(1, 1 + floor((avail_h - size) / pitch_y))

    array_w = _snap((nc - 1) * pitch_x + size)
    array_h = _snap((nr - 1) * pitch_y + size)

    ox = _snap((x0 + x1) / 2 - array_w / 2)
    oy = _snap((y0 + y1) / 2 - array_h / 2)

    for r in range(nr):
        for col in range(nc):
            x = _snap(ox + col * pitch_x)
            y = _snap(oy + r * pitch_y)
            _rect(c, LAYER.mcondrawing, x, y, x + size, y + size)


# ---------------------------------------------------------------------------
# Head/tail poly licon contacts for generic poly resistor
# ---------------------------------------------------------------------------


def _poly_head_licons(
    c: gf.Component,
    W: float,
    licon_y0: float,
    licon_y1: float,
) -> None:
    """Place licon contacts on the poly head/tail contact region.

    Magic uses enclosure = 0.12 (poly surround of 0.08 + extra) for poly
    head contacts, giving fewer contacts than the guard ring bars.
    """
    size = 0.17
    pitch = 0.34
    enc = 0.08  # poly surround of licon contact

    avail = _snap(W - 2 * enc)
    nc = max(1, 1 + floor((avail - size) / pitch)) if avail >= size else 0
    if nc < 1:
        return

    array_w = _snap((nc - 1) * pitch + size)
    x_start = _snap(-array_w / 2)

    # Place contacts — 1 row, nc columns
    y = _snap((licon_y0 + licon_y1) / 2 - size / 2)
    for i in range(nc):
        x = _snap(x_start + i * pitch)
        _rect(c, LAYER.licon1drawing, x, y, x + size, y + size)


def _diff_head_licons(
    c: gf.Component,
    W: float,
    licon_y0: float,
    licon_y1: float,
) -> None:
    """Place licon contacts on the diffusion head/tail contact region."""
    size = 0.17
    pitch = 0.34
    enc = 0.06

    avail = _snap(W - 2 * enc)
    nc = max(1, 1 + floor((avail - size) / pitch)) if avail >= size else 0
    if nc < 1:
        return

    array_w = _snap((nc - 1) * pitch + size)
    x_start = _snap(-array_w / 2)

    y = _snap((licon_y0 + licon_y1) / 2 - size / 2)
    for i in range(nc):
        x = _snap(x_start + i * pitch)
        _rect(c, LAYER.licon1drawing, x, y, x + size, y + size)


# ===========================================================================
# res_generic_po — standard poly resistor (~48 ohm/sq)
# ===========================================================================


[docs] @gf.cell def sky130_fd_pr__res_generic_po( res_width: float = 0.33, res_length: float = 1.65, ) -> gf.Component: """Return a standard poly resistor matching Magic VLSI geometry. Geometry: poly body centred at origin with head/tail poly contacts, NPC over contact ends, RPM (polyres) over resistor body, pwell guard ring. Args: res_width: width of the resistor body (W) in um. res_length: length of the resistor body (L) in um. .. plot:: :include-source: import sky130 c = sky130.pcells.sky130_fd_pr__res_generic_po() c.plot() """ c = gf.Component() W = res_width L = res_length hw = _snap(W / 2) # ---- Key derived dimensions ---- contact_region = 2.150 # poly extension beyond RPM for contacts poly_half = _snap(L / 2 + contact_region) gr_gap = 0.480 # gap between poly edge and guard ring inner edge gr_inner_x = _snap(hw + gr_gap) gr_inner_y = _snap(poly_half + gr_gap) inner_w = _snap(2 * gr_inner_x) inner_h = _snap(2 * gr_inner_y) # ---- Poly body (66, 20) — full length including contact heads ---- _rect(c, LAYER.polydrawing, -hw, -poly_half, hw, poly_half) # ---- RPM / polyres (66, 13) — over resistor body only ---- _rect(c, LAYER.polyres, -hw, -L / 2, hw, L / 2) # ---- NPC (95, 20) — over poly contact ends ---- # NPC half-width = outermost licon edge + 0.100 # (For head licons with enc=0.08: outer edge = (nc-1)*pitch/2 + 0.17/2) enc_licon = 0.08 avail_licon = _snap(W - 2 * enc_licon) nc_licon = ( max(1, 1 + floor((avail_licon - 0.17) / 0.34)) if avail_licon >= 0.17 else 1 ) licon_array_hw = _snap(((nc_licon - 1) * 0.34 + 0.17) / 2) npc_hw = _snap(licon_array_hw + 0.100) npc_h = 0.370 # NPC height npc_top = _snap(poly_half + 0.020) npc_bot = _snap(npc_top - npc_h) _rect(c, LAYER.npcdrawing, -npc_hw, npc_bot, npc_hw, npc_top) # top _rect(c, LAYER.npcdrawing, -npc_hw, -npc_top, npc_hw, -npc_bot) # bottom # ---- Guard ring ---- # Horizontal bar licons use fit_width = W + 0.240 _pwell_guard_ring(c, inner_w, inner_h, licon_fit_w=_snap(W + 0.240)) # ---- Head/tail poly licon contacts (66, 44) ---- licon_cap_bot = _snap(poly_half - 0.250) licon_cap_top = _snap(licon_cap_bot + _LICON) _poly_head_licons(c, W, licon_cap_bot, licon_cap_top) # top head _poly_head_licons(c, W, -licon_cap_top, -licon_cap_bot) # bottom head # ---- Li1 head/tail (67, 20) ---- # Cap (full poly width) over licon _rect(c, LAYER.li1drawing, -hw, licon_cap_bot, hw, licon_cap_top) # top cap _rect(c, LAYER.li1drawing, -hw, -licon_cap_top, hw, -licon_cap_bot) # bottom cap # Vertical strip connecting cap to met1 area li_strip_hw = _snap(hw - 0.080) li_strip_bot = _snap(L / 2 + 0.085) li_strip_top = licon_cap_bot _rect( c, LAYER.li1drawing, -li_strip_hw, li_strip_bot, li_strip_hw, li_strip_top ) # top _rect( c, LAYER.li1drawing, -li_strip_hw, -li_strip_top, li_strip_hw, -li_strip_bot ) # bottom # ---- Met1 (68, 20) ---- m1_hw = _snap(hw - 0.050) m1_inner = _snap(L / 2 + 0.025) m1_outer = _snap(poly_half - 0.020) _rect(c, LAYER.met1drawing, -m1_hw, m1_inner, m1_hw, m1_outer) # top _rect(c, LAYER.met1drawing, -m1_hw, -m1_outer, m1_hw, -m1_inner) # bottom # ---- Mcon contacts (67, 44) ---- _mcon_array(c, -m1_hw, m1_inner, m1_hw, m1_outer) # top _mcon_array(c, -m1_hw, -m1_outer, m1_hw, -m1_inner) # bottom # ---- Boundary marker (235, 4) ---- bnd_x = _snap(gr_inner_x + _GR_RING_W - 0.085) bnd_y = _snap(gr_inner_y + _GR_RING_W - 0.085) _rect(c, LAYER.prBoundaryboundary, -bnd_x, -bnd_y, bnd_x, bnd_y) # ---- Labels (match Magic output on li1label) ---- mcon_cy = _snap((m1_inner + m1_outer) / 2) gr_bottom = _snap(-(gr_inner_y + _GR_RING_W)) c.add_label(text="R1", position=(0, mcon_cy), layer=LAYER.li1label) c.add_label(text="R2", position=(0, -mcon_cy), layer=LAYER.li1label) c.add_label(text="B", position=(0, gr_bottom), layer=LAYER.li1label) # ---- Ports ---- c.add_port( name="PLUS", center=(0, m1_outer), width=W, orientation=90, layer=LAYER.met1drawing, ) c.add_port( name="MINUS", center=(0, -m1_outer), width=W, orientation=270, layer=LAYER.met1drawing, ) return c
# =========================================================================== # res_high_po_0p35 — high-resistance poly resistor # ===========================================================================
[docs] @gf.cell def sky130_fd_pr__res_high_po_0p35( res_width: float = 0.35, res_length: float = 0.50, ) -> gf.Component: """Return a high-resistance poly resistor matching Magic VLSI geometry. Different from generic_po: uses rpmdrawing (86,20), single NPC covering entire poly, solid PSDM, continuous licon contact strips, and different contact region dimensions. Args: res_width: width of the resistor body (W) in um. res_length: length of the resistor body (L) in um. .. plot:: :include-source: import sky130 c = sky130.pcells.sky130_fd_pr__res_high_po_0p35() c.plot() """ c = gf.Component() W = res_width L = res_length hw = _snap(W / 2) # ---- Key derived dimensions ---- contact_region = 2.080 poly_half = _snap(L / 2 + contact_region) gr_gap = 0.480 gr_inner_x = _snap(hw + gr_gap) gr_inner_y = _snap(poly_half + gr_gap) inner_w = _snap(2 * gr_inner_x) inner_h = _snap(2 * gr_inner_y) # ---- Poly body (66, 20) ---- _rect(c, LAYER.polydrawing, -hw, -poly_half, hw, poly_half) # ---- RPM (86, 20) — extends 0.200 beyond poly ---- rpm_enc = 0.200 _rect( c, LAYER.rpmdrawing, -hw - rpm_enc, -poly_half - rpm_enc, hw + rpm_enc, poly_half + rpm_enc, ) # ---- polyres (66, 13) — just the resistor body ---- _rect(c, LAYER.polyres, -hw, -L / 2, hw, L / 2) # ---- NPC (95, 20) — single rectangle covering entire poly + 0.095 ---- npc_enc = 0.095 _rect( c, LAYER.npcdrawing, -hw - npc_enc, -poly_half - npc_enc, hw + npc_enc, poly_half + npc_enc, ) # ---- Guard ring with solid PSDM ---- _pwell_guard_ring_solid_psdm(c, inner_w, inner_h, licon_fit_w=_snap(W + 0.240)) # ---- Continuous licon strips (66, 44) on poly head/tail ---- # Strip width = W - 2*0.080 = W - 0.160 licon_strip_hw = _snap(hw - 0.080) licon_strip_len = 2.000 # fixed length licon_strip_bot = _snap(L / 2) licon_strip_top = _snap(licon_strip_bot + licon_strip_len) # Top head _rect( c, LAYER.licon1drawing, -licon_strip_hw, licon_strip_bot, licon_strip_hw, licon_strip_top, ) # Bottom head _rect( c, LAYER.licon1drawing, -licon_strip_hw, -licon_strip_top, licon_strip_hw, -licon_strip_bot, ) # ---- Li1 head/tail (67, 20) — full poly width ---- li_bot = _snap(L / 2 - 0.080) li_top = poly_half _rect(c, LAYER.li1drawing, -hw, li_bot, hw, li_top) # top _rect(c, LAYER.li1drawing, -hw, -li_top, hw, -li_bot) # bottom # ---- Met1 (68, 20) ---- m1_hw = _snap(hw - 0.050) m1_inner = _snap(L / 2 - 0.055) m1_outer = _snap(poly_half - 0.030) _rect(c, LAYER.met1drawing, -m1_hw, m1_inner, m1_hw, m1_outer) # top _rect(c, LAYER.met1drawing, -m1_hw, -m1_outer, m1_hw, -m1_inner) # bottom # ---- Mcon contacts (67, 44) ---- _mcon_array(c, -m1_hw, m1_inner, m1_hw, m1_outer) # top _mcon_array(c, -m1_hw, -m1_outer, m1_hw, -m1_inner) # bottom # ---- Boundary marker (235, 4) ---- bnd_x = _snap(gr_inner_x + _GR_RING_W - 0.085) bnd_y = _snap(gr_inner_y + _GR_RING_W - 0.085) _rect(c, LAYER.prBoundaryboundary, -bnd_x, -bnd_y, bnd_x, bnd_y) # ---- Labels ---- mcon_cy = _snap((m1_inner + m1_outer) / 2) gr_bottom = _snap(-(gr_inner_y + _GR_RING_W)) c.add_label(text="R1", position=(0, mcon_cy), layer=LAYER.li1label) c.add_label(text="R2", position=(0, -mcon_cy), layer=LAYER.li1label) c.add_label(text="B", position=(0, gr_bottom), layer=LAYER.li1label) # ---- Ports ---- c.add_port( name="PLUS", center=(0, m1_outer), width=W, orientation=90, layer=LAYER.met1drawing, ) c.add_port( name="MINUS", center=(0, -m1_outer), width=W, orientation=270, layer=LAYER.met1drawing, ) return c
# Keep backward compatibility alias sky130_fd_pr__res_high_po = sky130_fd_pr__res_high_po_0p35 # =========================================================================== # res_generic_nd — N+ diffusion resistor # ===========================================================================
[docs] @gf.cell def sky130_fd_pr__res_generic_nd( res_width: float = 0.42, res_length: float = 2.10, ) -> gf.Component: """Return an N+ diffusion resistor matching Magic VLSI geometry. Geometry: diffusion body centred at origin with licon contacts at head/tail, NSDM implant, diffres marker, and pwell guard ring. Args: res_width: width of the resistor body (W) in um. res_length: length of the resistor body (L) in um. .. plot:: :include-source: import sky130 c = sky130.pcells.sky130_fd_pr__res_generic_nd() c.plot() """ c = gf.Component() W = res_width L = res_length hw = _snap(W / 2) # ---- Key derived dimensions ---- contact_region = 0.515 diff_half = _snap(L / 2 + contact_region) gr_gap = 0.440 gr_inner_x = _snap(hw + gr_gap) gr_inner_y = _snap(diff_half + gr_gap) inner_w = _snap(2 * gr_inner_x) inner_h = _snap(2 * gr_inner_y) # ---- Diffusion body (65, 20) ---- _rect(c, LAYER.diffdrawing, -hw, -diff_half, hw, diff_half) # ---- diffres (65, 13) — resistor marker ---- _rect(c, LAYER.diffres, -hw, -L / 2, hw, L / 2) # ---- NSDM (93, 44) — N+ implant extending 0.125 beyond diff ---- nsdm_enc = 0.125 _rect( c, LAYER.nsdmdrawing, -hw - nsdm_enc, -diff_half - nsdm_enc, hw + nsdm_enc, diff_half + nsdm_enc, ) # ---- Guard ring ---- _pwell_guard_ring(c, inner_w, inner_h, licon_fit_w=_snap(W + 0.240)) # ---- Head/tail licon contacts on diff (66, 44) ---- licon_enc_diff = 0.060 licon_top = _snap(diff_half - licon_enc_diff) licon_bot = _snap(licon_top - _LICON) _diff_head_licons(c, W, licon_bot, licon_top) # top head _diff_head_licons(c, W, -licon_top, -licon_bot) # bottom head # ---- Li1 head/tail (67, 20) ---- # Cap over licon area li_cap_hw = _snap(hw + 0.020) _rect(c, LAYER.li1drawing, -li_cap_hw, licon_bot, li_cap_hw, licon_top) # top cap _rect( c, LAYER.li1drawing, -li_cap_hw, -licon_top, li_cap_hw, -licon_bot ) # bottom cap # Short connecting strip below the cap li_strip_hw = _snap(hw - 0.060) li_strip_h = 0.200 li_strip_top = licon_bot li_strip_bot = _snap(li_strip_top - li_strip_h) _rect( c, LAYER.li1drawing, -li_strip_hw, li_strip_bot, li_strip_hw, li_strip_top ) # top _rect( c, LAYER.li1drawing, -li_strip_hw, -li_strip_top, li_strip_hw, -li_strip_bot ) # bottom # ---- Met1 (68, 20) ---- # Met1 dimensions scale with diffusion size: # hw = W/2 - 0.030 for narrow, W/2 for wide (when W >= ~0.7) # outer = diff_half for narrow, diff_half - 0.030 for wide # inner = outer - met1_height # met1 height for W=0.42: 0.490, for W=1.0: 0.430 m1_hw = _snap(hw - 0.030) m1_outer = _snap(diff_half) m1_inner = _snap(L / 2 + 0.025) # Adjust for wide devices: met1 expands to full diffusion width # and outer retracts by 0.030 if W >= 0.70: m1_hw = hw m1_outer = _snap(diff_half - 0.030) m1_inner = _snap(m1_outer - 0.430) _rect(c, LAYER.met1drawing, -m1_hw, m1_inner, m1_hw, m1_outer) _rect(c, LAYER.met1drawing, -m1_hw, -m1_outer, m1_hw, -m1_inner) # ---- Mcon contacts (67, 44) ---- _nd_mcon_array(c, -m1_hw, m1_inner, m1_hw, m1_outer) _nd_mcon_array(c, -m1_hw, -m1_outer, m1_hw, -m1_inner) # ---- Boundary marker (235, 4) ---- bnd_x = _snap(gr_inner_x + _GR_RING_W - 0.085) bnd_y = _snap(gr_inner_y + _GR_RING_W - 0.085) _rect(c, LAYER.prBoundaryboundary, -bnd_x, -bnd_y, bnd_x, bnd_y) # ---- Labels ---- mcon_cy = _snap((m1_inner + m1_outer) / 2) gr_bottom = _snap(-(gr_inner_y + _GR_RING_W)) c.add_label(text="R1", position=(0, mcon_cy), layer=LAYER.li1label) c.add_label(text="R2", position=(0, -mcon_cy), layer=LAYER.li1label) c.add_label(text="B", position=(0, gr_bottom), layer=LAYER.li1label) # ---- Ports ---- c.add_port( name="PLUS", center=(0, m1_outer), width=W, orientation=90, layer=LAYER.met1drawing, ) c.add_port( name="MINUS", center=(0, -m1_outer), width=W, orientation=270, layer=LAYER.met1drawing, ) return c
if __name__ == "__main__": c = sky130_fd_pr__res_generic_po() c.show()