diff --git a/src/glayout/cells/elementary/transmission_gate/transmission_gate.py b/src/glayout/cells/elementary/transmission_gate/transmission_gate.py index e45e13fc..460e869c 100644 --- a/src/glayout/cells/elementary/transmission_gate/transmission_gate.py +++ b/src/glayout/cells/elementary/transmission_gate/transmission_gate.py @@ -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 @@ -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) diff --git a/src/glayout/util/label_utils.py b/src/glayout/util/label_utils.py new file mode 100644 index 00000000..55def7c6 --- /dev/null +++ b/src/glayout/util/label_utils.py @@ -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 ``_pin`` / +``_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 ``_pin`` and the text on ``_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