From 24eebef60b73cdb78e9c398d4d724568d25b7e18 Mon Sep 17 00:00:00 2001 From: Nimish Kapoor <67710754+Nimok15@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:35:13 +0530 Subject: [PATCH] resistor.py --- src/glayout/primitives/resistor.py | 264 ++++++++++++++++------------- 1 file changed, 143 insertions(+), 121 deletions(-) diff --git a/src/glayout/primitives/resistor.py b/src/glayout/primitives/resistor.py index 2bcd85a5..bb8a6959 100644 --- a/src/glayout/primitives/resistor.py +++ b/src/glayout/primitives/resistor.py @@ -1,14 +1,51 @@ -from glayout.primitives.fet import pmos, nmos +from glayout import MappedPDK, sky130,gf180 +from gdsfactory.components import rectangle +from glayout.util.comp_utils import align_comp_to_port +from glayout.primitives.fet import pmos from glayout.routing.c_route import c_route -from glayout.routing.L_route import L_route from glayout.routing.straight_route import straight_route -from glayout.routing.smart_route import smart_route -from glayout.pdk.mappedpdk import MappedPDK from glayout.primitives.guardring import tapring from glayout.util.comp_utils import evaluate_bbox, add_ports_perimeter +from glayout.pdk.mappedpdk import MappedPDK from gdsfactory.component import Component -from gdsfactory.cell import cell +from gdsfactory.cell import cell from typing import Optional +from glayout.spice.netlist import Netlist +from gdsfactory.component import Component +import time +def add_resistor_labels(res: Component, pdk: MappedPDK) -> Component: + res.unlock() + + def label(txt): + r = rectangle(layer=pdk.get_glayer("met2_pin"), size=(0.3, 0.3), centered=True).copy() + r.add_label(txt, layer=pdk.get_glayer("met2_label")) + return r + + vres_port = next(p for n, p in res.ports.items() if n.startswith("VRES_")) + vss_port = next(p for n, p in res.ports.items() if n.startswith("VSS_")) + b_port = next(p for n, p in res.ports.items() if n.startswith("B_")) + + res.add(align_comp_to_port(label("VRES"), vres_port)) + res.add(align_comp_to_port(label("VSS"), vss_port)) + res.add(align_comp_to_port(label("B"), b_port)) + + return res.flatten() + +def resistor_netlist(fet: Component) -> Netlist: + """ + Diode-connected PFET resistor netlist + """ + resistor_netlist = Netlist(circuit_name="DIODE_RES", nodes=["VRES", "VSS", "B"]) + resistor_netlist.connect_netlist( + fet.info["netlist"], + [ + ("D", "VRES"), + ("G", "VRES"), + ("S", "VSS"), + ("B", "B"), + ], + ) + return resistor_netlist @cell def resistor( @@ -16,124 +53,109 @@ def resistor( width: float = 5, length: float = 1, num_series: int = 1, - with_substrate_tap: bool = False, - with_tie: bool = False, - with_dnwell: bool = False, - rmult: Optional[int] = None, multipliers: int = 1, - substrate_tap_layers: tuple[str, str] = ("met2", "met1"), + rmult: Optional[int] = None, + with_tie: bool = True, + with_substrate_tap: bool = False, tie_layers: tuple[str, str] = ("met2", "met1"), + substrate_tap_layers: tuple[str, str] = ("met2", "met1"), ) -> Component: - """This cell represents a diode connected pfet which acts as a programmable resistor. The small signal resistance is modelled by (1/gm)||r_0, where gm is the fet's transconductance and r0 is the small signal output impedance. The cell can be instantiated with any choice of width and length. The number of resistors connected in series can be controlled using numseries (**note: they will be placed in a single line, so area saving has to be manually handled). The number of resistors in parallel connnection can be controlled using multipliers. - Note that parallel and series resistors can be used simultaneously, but the parallel-ness will be applied to all resistors in series. The cell can be used and routed separately should a more complex combination of resistances be required - - Args: - pdk (MappedPDK): the process design kit to be used - width (float, optional): the width of each pfet. Defaults to 5. - length (float, optional): the length of each pfet. Defaults to 1. - num_series (int, optional): the number of pfets connected in series. Defaults to 1. - with_substrate_tap (bool, optional): the presence of substrate tap. Defaults to False. - with_tie (bool, optional): the presence of tie. Defaults to False. - with_dnwell (bool, optional): the presence of dnwell. Defaults to False. - rmult (Optional[int], optional): the routing multiplier (controls routing width). Defaults to None. - multipliers (int, optional): the number of pfets connected in parallel. Defaults to 1. - substrate_tap_layers (tuple[str, str], optional): the layers in the substrate tapring. Defaults to ("met2", "met1"). - tie_layers (tuple[str, str], optional): the layers in the tie layer tapring. Defaults to ("met2", "met1"). - - Returns: - Component: an instance of the resistor cell """ - toplvl = Component() + Diode-connected PFET resistor (CM-style) + + Ports: + - VRES : drain/gate node + - VSS : source + - B : bulk + """ + pdk.activate() + res = Component("resistor") max_sep = pdk.util_max_metal_seperation() - if num_series == 1: - pfet_reference = toplvl << pmos(pdk, width=width, length=length, with_substrate_tap=with_substrate_tap, with_tie=with_tie, dnwell=with_dnwell, rmult=rmult, multipliers=multipliers, substrate_tap_layers=substrate_tap_layers, tie_layers=tie_layers, with_dummy=False) - toplvl.add_ports(pfet_reference.ports, prefix='pfet_') - - # short gate and drain - diode_connect = toplvl << c_route(pdk, pfet_reference.ports['multiplier_0_gate_W'], pfet_reference.ports['multiplier_0_drain_W']) - - else: - pfet_references = [] - diode_connect_references = [] - pfet_reference_0 = toplvl << pmos(pdk, width=width, length=length, with_substrate_tap=False, with_tie=False, dnwell=False, rmult=rmult, multipliers=multipliers, substrate_tap_layers=substrate_tap_layers, tie_layers=tie_layers, with_dummy=False) - diode_connect_0 = toplvl << c_route(pdk, pfet_reference_0.ports['multiplier_0_gate_W'], pfet_reference_0.ports['multiplier_0_drain_W']) - diode_connect_references.append(diode_connect_0) - - toplvl.add_ports(pfet_reference_0.ports, prefix='pfet_0_') - pfet_references.append(pfet_reference_0) - for i in range(1, num_series): - pfet_reference = (toplvl << pmos(pdk, width=width, length=length, with_substrate_tap=False, with_tie=False, dnwell=False, rmult=rmult,multipliers=multipliers, substrate_tap_layers=substrate_tap_layers, tie_layers=tie_layers, with_dummy=False)).movey(i * (evaluate_bbox(pfet_reference_0)[1] + max_sep)) - - pfet_references.append(pfet_reference) - if i < num_series - 1: - toplvl.add_ports(pfet_reference.ports, prefix=f'pfet_{i}_') - - # short gate and drain - diode_connect = toplvl << c_route(pdk, pfet_reference.ports['multiplier_0_gate_W'], pfet_reference.ports['multiplier_0_drain_W']) - diode_connect_references.append(diode_connect) - # connect drain and source of previous and present pfet - if multipliers > 1: - extension = 1 * ((i % 2) + 1) - else: - extension = 0.5 * ((i % 2)) - toplvl << c_route(pdk, pfet_references[i-1].ports['multiplier_0_source_E'], pfet_reference.ports['multiplier_0_drain_E'], extension=extension) - - # add tie if tie - if with_tie: - tap_separation = max( - pdk.get_grule("met2")["min_separation"], - pdk.get_grule("met1")["min_separation"], - pdk.get_grule("active_diff", "active_tap")["min_separation"], - ) - tap_separation += pdk.get_grule("n+s/d", "active_tap")["min_enclosure"] - tap_encloses = ( - (evaluate_bbox(toplvl)[0] + max_sep), - (evaluate_bbox(toplvl)[1] + max_sep), - ) - ringtoadd = tapring( - pdk, - enclosed_rectangle=tap_encloses, - sdlayer="n+s/d", - horizontal_glayer=tie_layers[0], - vertical_glayer=tie_layers[1], - ) - tapring_ref = (toplvl << ringtoadd).movey(((evaluate_bbox(pfet_reference_0)[1] + max_sep) * ((num_series - 1)/2) )) - toplvl.add_ports(tapring_ref.get_ports_list(),prefix="tie_") - for row in range(multipliers): - for dummyside, tieside in [("L","W"),("R","E")]: - try: - toplvl << straight_route(pdk, toplvl.ports[f"multiplier_{row}_dummy_{dummyside}_gsdcon_top_met_W"], toplvl.ports[f"tie_{tieside}_top_met_{tieside}"],glayer2="met1") - except KeyError: - pass - - # add nwell - nwell_glayer = "dnwell" if with_dnwell else "nwell" - toplvl.add_padding( - layers=(pdk.get_glayer(nwell_glayer),), - default=pdk.get_grule("active_tap", nwell_glayer)["min_enclosure"], - ) - toplvl = add_ports_perimeter(toplvl, layer=pdk.get_glayer(nwell_glayer),prefix="well_") - - # add substrate tap if needed - if with_substrate_tap: - substrate_tap_separation = pdk.get_grule("dnwell", "active_tap")[ - "min_separation" - ] - substrate_tap_encloses = ( - (evaluate_bbox(toplvl)[0] + max_sep), - (evaluate_bbox(toplvl)[1] + max_sep), - ) - ringtoadd = tapring( - pdk, - enclosed_rectangle=substrate_tap_encloses, - sdlayer="p+s/d", - horizontal_glayer=substrate_tap_layers[0], - vertical_glayer=substrate_tap_layers[1], - ) - tapring_ref = (toplvl << ringtoadd).movey(((evaluate_bbox(pfet_reference_0)[1] + max_sep) * ((num_series - 1)/2) )) - toplvl.add_ports(tapring_ref.get_ports_list(),prefix="guardring_") - - toplvl.add_ports(pfet_references[0].get_ports_list(), prefix='port1_') - toplvl.add_ports(pfet_references[-1].get_ports_list(), prefix='port2_') - - return toplvl \ No newline at end of file + pfets = [] + + # ---- First PFET ---- + pf0 = res << pmos( + pdk, + width=width, + length=length, + multipliers=multipliers, + rmult=rmult, + with_dummy=False, + with_tie=False, + with_substrate_tap=False, + ) + pfets.append(pf0) + + # Diode connect + diode0 = res << c_route(pdk, pf0.ports["multiplier_0_gate_W"], pf0.ports["multiplier_0_drain_W"]) + + # ---- Series stacking ---- + for i in range(1, num_series): + pf = (res << pmos( + pdk, + width=width, + length=length, + multipliers=multipliers, + rmult=rmult, + with_dummy=False, + with_tie=False, + with_substrate_tap=False, + )).movey(i * (evaluate_bbox(pf0)[1] + max_sep)) + + pfets.append(pf) + + # diode connect + res << c_route(pdk, pf.ports["multiplier_0_gate_W"], pf.ports["multiplier_0_drain_W"]) + + # connect previous source → next drain + res << c_route(pfets[i - 1].ports["multiplier_0_source_E"], pf.ports["multiplier_0_drain_E"]) + + # ---- Canonical ports ---- + res.add_ports([pfets[0].ports["multiplier_0_source_E"]], prefix="VSS_") + res.add_ports(diode0.get_ports_list(), prefix="VRES_") + + # ---- Well / bulk ---- + res.add_padding(layers=(pdk.get_glayer("nwell"),), default=pdk.get_grule("active_tap", "nwell")["min_enclosure"]) + res = add_ports_perimeter(res, layer=pdk.get_glayer("nwell"), prefix="B_") + + # ---- Tie ring ---- + if with_tie: + tap_encloses = (evaluate_bbox(res)[0] + max_sep, evaluate_bbox(res)[1] + max_sep) + ring = tapring(pdk, enclosed_rectangle=tap_encloses, sdlayer="n+s/d", horizontal_glayer=tie_layers[0], vertical_glayer=tie_layers[1]) + ringref = res << ring + res.add_ports(ringref.get_ports_list(), prefix="tie_") + + # ---- Substrate tap ---- + if with_substrate_tap: + tap_encloses = (evaluate_bbox(res)[0] + max_sep, evaluate_bbox(res)[1] + max_sep) + ring = tapring(pdk, enclosed_rectangle=tap_encloses, sdlayer="p+s/d", horizontal_glayer=substrate_tap_layers[0], vertical_glayer=substrate_tap_layers[1]) + ringref = res << ring + res.add_ports(ringref.get_ports_list(), prefix="substrate_") + + # ---- Netlist ---- + res.info["netlist"] = resistor_netlist(pfets[0]) + + return res + +import time +if __name__ == "__main__": + comp =resistor(sky130) + # comp.pprint_ports() + comp =add_resistor_labels(comp,sky130) + comp = comp.flatten() + comp.name = "RS" + #print(comp.info['netlist'].generate_netlist()) + nl = comp.info["netlist"] + print(nl) + print(nl.generate_netlist()) + print("...Running DRC...") + drc_result = sky130.drc_magic(comp, "RS") + print(drc_result) + ## Klayout DRC + #drc_result = sky130.drc(comp)\n + time.sleep(5) + print("...Running LVS...") + lvs_res=sky130.lvs_netgen(comp, "RS", copy_intermediate_files=True, show_scripts=True) + print(lvs_res) + #print("...Saving GDS...") + #comp.write_gds('out_resistor.gds')