Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 32 additions & 145 deletions src/glayout/cells/elementary/transmission_gate/transmission_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,144 +11,48 @@
from glayout.util.port_utils import add_ports_perimeter
from glayout.spice.netlist import Netlist
from glayout.primitives.via_gen import via_stack
from glayout.util.label_utils import add_pin_labels, LabelSpec
try:
from glayout.verification.evaluator_wrapper import run_evaluation
except ImportError:
print("Warning: evaluator_wrapper not found. Evaluation will be skipped.")
run_evaluation = None

def add_tg_labels(tg_in: Component,
pdk: MappedPDK
) -> Component:

tg_in.unlock()

# list that will contain all port/comp info
move_info = list()
# create labels and append to info list
# vin
vinlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
vinlabel.add_label(text="VIN",layer=pdk.get_glayer("met2_label"))
move_info.append((vinlabel,tg_in.ports["N_multiplier_0_source_E"],None))

# vout
voutlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
voutlabel.add_label(text="VOUT",layer=pdk.get_glayer("met2_label"))
move_info.append((voutlabel,tg_in.ports["P_multiplier_0_drain_W"],None))

# vcc
vcclabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.5,0.5),centered=True).copy()
vcclabel.add_label(text="VCC",layer=pdk.get_glayer("met2_label"))
move_info.append((vcclabel,tg_in.ports["P_tie_S_top_met_S"],None))

# vss
vsslabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.5,0.5),centered=True).copy()
vsslabel.add_label(text="VSS",layer=pdk.get_glayer("met2_label"))
move_info.append((vsslabel,tg_in.ports["N_tie_S_top_met_N"], None))

# VGP
vgplabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
vgplabel.add_label(text="VGP",layer=pdk.get_glayer("met2_label"))
move_info.append((vgplabel,tg_in.ports["P_multiplier_0_gate_E"], None))

# VGN
vgnlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
vgnlabel.add_label(text="VGN",layer=pdk.get_glayer("met2_label"))
move_info.append((vgnlabel,tg_in.ports["N_multiplier_0_gate_E"], None))

# move everything to position
for comp, prt, alignment in move_info:
alignment = ('c','b') if alignment is None else alignment
compref = align_comp_to_port(comp, prt, alignment=alignment)
tg_in.add(compref)
return tg_in.flatten()
_TG_LABELS = [
LabelSpec("VIN", "N_multiplier_0_source_E", size=0.27),
LabelSpec("VOUT", "P_multiplier_0_drain_W", size=0.27),
LabelSpec("VCC", "P_tie_S_top_met_S", size=0.5),
LabelSpec("VSS", "N_tie_S_top_met_N", size=0.5),
LabelSpec("VGP", "P_multiplier_0_gate_E", size=0.27),
LabelSpec("VGN", "N_multiplier_0_gate_E", size=0.27),
]


def get_component_netlist(component):
"""Helper function to get netlist object from component info, compatible with all gdsfactory versions"""
from glayout.spice.netlist import Netlist

# Try to get stored object first (for older gdsfactory versions)
if 'netlist_obj' in component.info:
return component.info['netlist_obj']

# Try to reconstruct from netlist_data (for newer gdsfactory versions)
if 'netlist_data' in component.info:
data = component.info['netlist_data']
netlist = Netlist(
circuit_name=data['circuit_name'],
nodes=data['nodes']
)
netlist.source_netlist = data['source_netlist']
return netlist

# Fallback: return the string representation (should not happen in normal operation)
return component.info.get('netlist', '')

def sky130_add_tg_labels(tg_in: Component) -> Component:

tg_in.unlock()

# define layers`
met1_pin = (68,16)
met1_label = (68,5)
li1_pin = (67,16)
li1_label = (67,5)
# list that will contain all port/comp info
move_info = list()
# create labels and append to info list
# vin
vinlabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
vinlabel.add_label(text="VIN",layer=met1_label)
move_info.append((vinlabel,tg_in.ports["N_multiplier_0_source_E"],None))

# vout
voutlabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
voutlabel.add_label(text="VOUT",layer=met1_label)
move_info.append((voutlabel,tg_in.ports["P_multiplier_0_drain_W"],None))

# vcc
vcclabel = rectangle(layer=met1_pin,size=(0.5,0.5),centered=True).copy()
vcclabel.add_label(text="VCC",layer=met1_label)
move_info.append((vcclabel,tg_in.ports["P_tie_S_top_met_S"],None))

# vss
vsslabel = rectangle(layer=met1_pin,size=(0.5,0.5),centered=True).copy()
vsslabel.add_label(text="VSS",layer=met1_label)
move_info.append((vsslabel,tg_in.ports["N_tie_S_top_met_N"], None))

# VGP
vgplabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
vgplabel.add_label(text="VGP",layer=met1_label)
move_info.append((vgplabel,tg_in.ports["P_multiplier_0_gate_E"], None))

# VGN
vgnlabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
vgnlabel.add_label(text="VGN",layer=met1_label)
move_info.append((vgnlabel,tg_in.ports["N_multiplier_0_gate_E"], None))

# move everything to position
for comp, prt, alignment in move_info:
alignment = ('c','b') if alignment is None else alignment
compref = align_comp_to_port(comp, prt, alignment=alignment)
tg_in.add(compref)
return tg_in.flatten()
def add_tg_labels(tg_in: Component, pdk: MappedPDK) -> Component:
"""Add LVS pin rectangles + text labels to a transmission gate (PDK-agnostic)."""
return add_pin_labels(tg_in, pdk, _TG_LABELS)


def tg_netlist(nfet: Component, pfet: Component) -> Netlist:

netlist = Netlist(circuit_name='Transmission_Gate', nodes=['VIN', 'VSS', 'VOUT', 'VCC', 'VGP', 'VGN'])
# Use helper function to get netlist objects regardless of gdsfactory version.
# Each fet's dummies physically tie to the fet's own welltie ring (NMOS
# bulk = VSS, PMOS bulk = VCC), so DUM gets mapped to the same bulk net
# so the schematic matches the layout extraction.
nfet_netlist = get_component_netlist(nfet)
pfet_netlist = get_component_netlist(pfet)
netlist.connect_netlist(nfet_netlist, [('D', 'VOUT'), ('G', 'VGN'), ('S', 'VIN'), ('B', 'VSS'), ('DUM', 'VSS')])
netlist.connect_netlist(pfet_netlist, [('D', 'VOUT'), ('G', 'VGP'), ('S', 'VIN'), ('B', 'VCC'), ('DUM', 'VCC')])

return netlist

netlist = Netlist(
circuit_name="Transmission_Gate",
nodes=["VIN", "VSS", "VOUT", "VCC", "VGP", "VGN"],
)
# Each fet's dummies physically tie to that fet's own welltie ring (NMOS bulk
# = VSS, PMOS bulk = VCC), so DUM maps to the same bulk net and the schematic
# matches the layout extraction. DUM is a real port of the subckt:
# `.subckt NMOS D G S B DUM`.
netlist.connect_netlist(
nfet.info["netlist"],
[("D", "VOUT"), ("G", "VGN"), ("S", "VIN"), ("B", "VSS"), ("DUM", "VSS")],
)
netlist.connect_netlist(
pfet.info["netlist"],
[("D", "VOUT"), ("G", "VGP"), ("S", "VIN"), ("B", "VCC"), ("DUM", "VCC")],
)

return netlist


@cell
Expand Down Expand Up @@ -228,26 +132,9 @@ def transmission_gate(
pass

return component
if __name__ == "__main__":
# OLD EVAL CODE
# comp = transmission_gate(sky130)
# # comp.pprint_ports()
# comp = add_tg_labels(comp,sky130)
# comp.name = "TG"
# comp.show()
# #print(comp.info['netlist'].generate_netlist())
# print("...Running DRC...")
# drc_result = sky130.drc_magic(comp, "TG")
# ## Klayout DRC
# #drc_result = gf180.drc(comp)\n

# time.sleep(5)

# print("...Running LVS...")
# lvs_res=sky130.lvs_netgen(comp, "TG")
# #print("...Saving GDS...")
# #comp.write_gds('out_TG.gds')


if __name__ == "__main__":
# NEW EVAL CODE
#transmission_gate = transmission_gate(sky130_mapped_pdk)
transmission_gate = add_tg_labels(transmission_gate(sky130),sky130)
Expand Down
152 changes: 152 additions & 0 deletions src/glayout/util/label_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""PDK-agnostic helpers for adding LVS pin rectangles and text labels to a cell.

Two labeling paradigms were previously copy-pasted across the codebase.

Both are unified here. Layers are resolved from the PDK's ``<glayer>_pin`` /
``<glayer>_label`` glayers, and the glayer defaults to the *port's own* metal
(``pdk.layer_to_glayer(port.layer)``) so a pin always lands on the same layer as the
port it marks -- correct on any PDK, and immune to sky130's glayer/li1 offset that the
old hardcoded constants could get wrong.
"""

import math
from dataclasses import dataclass
from typing import Iterable, Optional, Union

from glayout.backend import Component, Port, rectangle
from glayout.pdk.mappedpdk import MappedPDK
from glayout.util.comp_utils import align_comp_to_port


@dataclass
class LabelSpec:
"""One label: which ``name`` goes on which ``port``.

port: a port name (looked up on the component) or a ``Port`` object.
glayer: generic layer (e.g. ``"met2"``); ``None`` derives it from the port.
size: pin-rectangle size, a float (square) or ``(x, y)``.
alignment: how the pin rect is aligned to the port (see ``align_comp_to_port``).
"""

name: str
port: Union[str, Port]
glayer: Optional[str] = None
size: Union[float, tuple[float, float]] = 0.5
alignment: tuple[str, str] = ("c", "b")


SpecLike = Union[LabelSpec, tuple]


def _as_spec(spec: SpecLike) -> LabelSpec:
"""Coerce a tuple into a LabelSpec, positionally in field order:
``(name, port[, glayer[, size[, alignment]]])``."""
if isinstance(spec, LabelSpec):
return spec
if isinstance(spec, tuple) and 2 <= len(spec) <= 5:
out = LabelSpec(*spec)
# `glayer` is the third field, but the label tables this replaced were
# (comp, port, alignment) triples. Catch a tuple landing in the glayer
# slot here rather than failing later inside get_glayer("('c', 'b')").
if out.glayer is not None and not isinstance(out.glayer, str):
raise TypeError(
f"glayer must be a glayer name, got {out.glayer!r}. Alignment is "
"the 5th element -- pass LabelSpec(name, port, alignment=...) instead."
)
return out
raise TypeError(f"cannot interpret label spec: {spec!r}")


def _as_xy(size: Union[float, tuple[float, float]]) -> tuple[float, float]:
return size if isinstance(size, tuple) else (size, size)


def _resolve_port(component: Component, port: Union[str, Port]) -> Port:
return component.ports[port] if isinstance(port, str) else port


def _port_glayer(pdk: MappedPDK, port: Port, glayer: Optional[str]) -> str:
return glayer if glayer is not None else pdk.layer_to_glayer(port.layer)


def add_pin_label(
component: Component,
pdk: MappedPDK,
port: Port,
name: str,
*,
glayer: Optional[str] = None,
size: Union[float, tuple[float, float]] = 0.5,
alignment: tuple[str, str] = ("c", "b"),
) -> Component:
"""Add a single LVS pin rectangle + text label at ``port``.

The rectangle is drawn on ``<glayer>_pin`` and the text on ``<glayer>_label``,
where ``glayer`` defaults to the port's own metal.
"""
glayer = _port_glayer(pdk, port, glayer)
pin = rectangle(
layer=pdk.get_glayer(f"{glayer}_pin"), size=_as_xy(size), centered=True
).copy()
pin.add_label(text=name, layer=pdk.get_glayer(f"{glayer}_label"))
component.add(align_comp_to_port(pin, port, alignment=alignment))
return component


def add_pin_labels(
component: Component,
pdk: MappedPDK,
specs: Iterable[SpecLike],
*,
flatten: bool = True,
) -> Component:
"""Add LVS pin rectangles + labels for every spec.

Replaces the ``move_info`` boilerplate in the ``sky130_add_*_labels`` functions;
each caller now supplies only its port->name data table.
"""
component.unlock()
for raw in specs:
spec = _as_spec(raw)
port = _resolve_port(component, spec.port)
add_pin_label(
component,
pdk,
port,
spec.name,
glayer=spec.glayer,
size=spec.size,
alignment=spec.alignment,
)
return component.flatten() if flatten else component


def expose_ports(
component: Component,
pdk: MappedPDK,
specs: Iterable[SpecLike],
*,
inset: float = 0.1,
) -> Component:
"""Register a named ``Port`` and drop an inward-offset text label for each spec.

Replaces the inline ``expose()`` closures in the gf180 cells: a routing port plus
a text label nudged ``inset`` um inside the shape (so Magic attaches it to the
underlying geometry). No pin rectangle is added.
"""
for raw in specs:
spec = _as_spec(raw)
port = _resolve_port(component, spec.port)
component.add_port(name=spec.name, port=port)
glayer = _port_glayer(pdk, port, spec.glayer)
angle = port.orientation
if angle is not None:
dx = -inset * math.cos(math.radians(angle))
dy = -inset * math.sin(math.radians(angle))
else:
dx, dy = 0.0, 0.0
pos = (port.center[0] + dx, port.center[1] + dy)
component.add_label(
text=spec.name, position=pos, layer=pdk.get_glayer(f"{glayer}_label")
)
return component