From bfe289bc31cf9c4a010fd4e39c4a2800a01dd55e Mon Sep 17 00:00:00 2001 From: Nimish Kapoor <67710754+Nimok15@users.noreply.github.com> Date: Mon, 29 Dec 2025 01:59:37 +0530 Subject: [PATCH] Netlist Auto for current mirror --- .../current_mirror/current_mirror.py | 300 ++++++++---------- 1 file changed, 127 insertions(+), 173 deletions(-) diff --git a/blocks/elementary/current_mirror/current_mirror.py b/blocks/elementary/current_mirror/current_mirror.py index 46b3ddbe..c1027886 100644 --- a/blocks/elementary/current_mirror/current_mirror.py +++ b/blocks/elementary/current_mirror/current_mirror.py @@ -15,219 +15,173 @@ import time -def add_cm_labels(cm_in: Component, - pdk: MappedPDK - ) -> Component: - +def add_cm_labels(cm_in: Component, pdk: MappedPDK) -> Component: cm_in.unlock() # list that will contain all port/comp info - move_info = list() - # create labels and append to info list - # vss - vsslabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + move_info = [] + + # VSS + vsslabel = rectangle(layer=pdk.get_glayer("met2_pin"), size=(0.27, 0.27), centered=True).copy() vsslabel.add_label(text="VSS",layer=pdk.get_glayer("met2_label")) - move_info.append((vsslabel,cm_in.ports["fet_A_source_E"],None)) - - # vref - vreflabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + move_info.append((vsslabel, cm_in.ports["ref_multiplier_0_source_E"])) + + # VREF + vreflabel = rectangle(layer=pdk.get_glayer("met2_pin"), size=(0.27, 0.27), centered=True).copy() vreflabel.add_label(text="VREF",layer=pdk.get_glayer("met2_label")) - move_info.append((vreflabel,cm_in.ports["fet_A_drain_N"],None)) - - # vcopy - vcopylabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + move_info.append((vreflabel, cm_in.ports["ref_multiplier_0_drain_N"])) + + # VCOPY + vcopylabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27, 0.27), centered=True).copy() vcopylabel.add_label(text="VCOPY",layer=pdk.get_glayer("met2_label")) - move_info.append((vcopylabel,cm_in.ports["fet_B_drain_N"],None)) - - # VB - vblabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.5,0.5),centered=True).copy() + move_info.append((vcopylabel, cm_in.ports["out_multiplier_0_drain_N"])) + + # VB (well tie) + vb_port = next(p for n, p in cm_in.ports.items() if n.startswith("well_")) + vblabel = rectangle(layer=pdk.get_glayer("met2_pin"), size=(0.5, 0.5), centered=True).copy() vblabel.add_label(text="VB",layer=pdk.get_glayer("met2_label")) - move_info.append((vblabel,cm_in.ports["welltie_S_top_met_S"], 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) - cm_in.add(compref) - return cm_in.flatten() - -def current_mirror_netlist( - pdk: MappedPDK, - width: float = 1, - length: float = None, - multipliers: int = 1, - with_dummy: bool = True, - n_or_p_fet: Optional[str] = 'nfet', - subckt_only: Optional[bool] = True, -) -> Netlist: - if length is None: - length = pdk.get_grule('poly')['min_width'] - if width is None: - width = 3 - mtop = multipliers if subckt_only else 1 - model = pdk.models[n_or_p_fet] - - source_netlist = """.subckt {circuit_name} {nodes} """ + f'l={length} w={width} m={mtop} ' + """ -XA VREF VREF VSS VB {model} l={{l}} w={{w}} m={{m}} -XB VCOPY VREF VSS VB {model} l={{l}} w={{w}} m={{m}}""" - if with_dummy: - source_netlist += "\nXDUMMY VB VB VB VB {model} l={{l}} w={{w}} m={{2}}" - source_netlist += "\n.ends {circuit_name}" - - instance_format = "X{name} {nodes} {circuit_name} l={length} w={width} m={mult}" - - return Netlist( - circuit_name='CMIRROR', - nodes=['VREF', 'VCOPY', 'VSS', 'VB'], - source_netlist=source_netlist, - instance_format=instance_format, - parameters={ - 'model': model, - 'width': width, - 'length': length, - 'mult': multipliers - } - ) + move_info.append((vblabel, vb_port)) + for label, port in move_info: + cm_in.add(align_comp_to_port(label, port, alignment=("c", "b"))) + + return cm_in.flatten() + +def current_mirror_netlist(fetL: Component, fetR: Component) -> Netlist: + current_mirror_netlist = Netlist(circuit_name='CMIRROR', nodes=['VREF', 'VOUT', 'VSS', 'B']) + current_mirror_netlist.connect_netlist( + fetL.info['netlist'], + [('D', 'VREF'), ('G', 'VREF'), ('S', 'VSS'), ('B', 'B')] + ) + current_mirror_netlist.connect_netlist( + fetR.info['netlist'], + [('D', 'VOUT'), ('G', 'VREF'), ('S', 'VSS'), ('B', 'B')] + ) + return current_mirror_netlist @cell def current_mirror( pdk: MappedPDK, numcols: int = 3, device: str = 'nfet', - with_dummy: Optional[bool] = True, + width: float = 3, + fingers: int = 1, + length: Optional[float] = None, + rmult: int = 1, + dummy: Union[bool, tuple[bool, bool]] = True, with_substrate_tap: Optional[bool] = False, with_tie: Optional[bool] = True, - tie_layers: tuple[str,str]=("met2","met1"), + tie_layers: tuple[str, str] = ("met2", "met1"), subckt_only: Optional[bool] = True, **kwargs ) -> Component: - """An instantiable current mirror that returns a Component object. The current mirror is a two transistor interdigitized structure with a shorted source and gate. It can be instantiated with either nmos or pmos devices. It can also be instantiated with a dummy device, a substrate tap, and a tie layer, and is centered at the origin. Transistor A acts as the reference and Transistor B acts as the mirror fet + """ + An instantiable current mirror that returns a Component object. + + The current mirror is a two-transistor structure with shorted + source and gate. It can be instantiated with either NMOS or PMOS + devices. Optional dummy devices, substrate tap, and tie ring + are supported. The layout is centered at the origin. + + Transistor A acts as the reference device, + Transistor B acts as the mirror device. Args: - pdk (MappedPDK): the process design kit to use - numcols (int): number of columns of the interdigitized fets - device (str): nfet or pfet (can only interdigitize one at a time with this option) - with_dummy (bool): True places dummies on either side of the interdigitized fets - with_substrate_tap (bool): boolean to decide whether to place a substrate tapring - with_tie (bool): boolean to decide whether to place a tapring for tielayer - tie_layers (tuple[str,str], optional): the layers to use for the tie. Defaults to ("met2","met1"). - **kwargs: The keyword arguments are passed to the two_nfet_interdigitized or two_pfet_interdigitized functions and need to be valid arguments that can be accepted by the multiplier function + pdk (MappedPDK): Process design kit + numcols (int): Number of columns + device (str): 'nfet' or 'pfet' + width (float): Device width + fingers (int): Number of fingers + length (float): Channel length + rmult (int): Multiplier + dummy (bool | tuple): Dummy devices + with_substrate_tap (bool): Add substrate tap + with_tie (bool): Add well tie + tie_layers (tuple): Tie metal layers + subckt_only (bool): Netlist control + **kwargs: Passed to device generator Returns: - Component: a current mirror component object + Component: Current mirror layout """ pdk.activate() - top_level = Component("current mirror") - if device.lower() in ['nmos', 'nfet']: - interdigitized_fets = two_nfet_interdigitized( - pdk, - numcols=numcols, - dummy=with_dummy, - with_substrate_tap=False, - with_tie=False, - **kwargs - ) - elif device.lower() in ['pmos', 'pfet']: - interdigitized_fets = two_pfet_interdigitized( - pdk, - numcols=numcols, - dummy=with_dummy, - with_substrate_tap=False, - with_tie=False, - **kwargs - ) + cmirror = Component("current_mirror") + + # Handle dummy specification + if isinstance(dummy, bool): + dummy = (dummy, dummy) + well = None + if device.lower() in ["nmos", "nfet"]: + fetL = nmos(pdk, width=width, fingers=fingers, length=length, multipliers=1, with_tie=False, with_dummy=(dummy[0], False), with_dnwell=False, with_substrate_tap=False, rmult=rmult) + fetR = nmos(pdk, width=width, fingers=fingers, length=length, multipliers=1, with_tie=False, with_dummy=(False, dummy[1]), with_dnwell=False, with_substrate_tap=False, rmult=rmult) + min_spacing_x = (pdk.get_grule("n+s/d")["min_separation"] - 2 * (fetL.xmax - fetL.ports["multiplier_0_plusdoped_E"].center[0])) + well = "pwell" + + elif device.lower() in ["pmos", "pfet"]: + fetL = pmos(pdk, width=width, fingers=fingers, length=length, multipliers=1, with_tie=False, with_dummy=(dummy[0], False), dnwell=False, with_substrate_tap=False, rmult=rmult) + fetR = pmos(pdk, width=width, fingers=fingers, length=length, multipliers=1, with_tie=False, with_dummy=(False, dummy[1]), dnwell=False, with_substrate_tap=False, rmult=rmult) + min_spacing_x = (pdk.get_grule("p+s/d")["min_separation"] - 2 * (fetL.xmax - fetL.ports["multiplier_0_plusdoped_E"].center[0])) + well = "nwell" + else: - raise ValueError(f"Device type {device} not recognized. Use 'nfet' or 'pfet'.") - - top_level.add_ports(interdigitized_fets.get_ports_list(), prefix="fet_") - maxmet_sep = pdk.util_max_metal_seperation() - # short source of the fets - source_short = interdigitized_fets << c_route(pdk, interdigitized_fets.ports['A_source_E'], interdigitized_fets.ports['B_source_E'], extension=3*maxmet_sep, viaoffset=False) - # short gates of the fets - gate_short = interdigitized_fets << c_route(pdk, interdigitized_fets.ports['A_gate_W'], interdigitized_fets.ports['B_gate_W'], extension=3*maxmet_sep, viaoffset=False) - # short gate and drain of one of the reference - interdigitized_fets << L_route(pdk, interdigitized_fets.ports['A_drain_W'], gate_short.ports['con_N'], viaoffset=False, fullbottom=False) - - top_level << interdigitized_fets - if with_tie: - if device.lower() in ['nmos','nfet']: - tap_layer = "p+s/d" - elif device.lower() in ['pmos','pfet']: - tap_layer = "n+s/d" - else: - raise ValueError(f"Device type {device} not recognized. Use 'nfet' or 'pfet'.") - - tap_sep = max( - pdk.util_max_metal_seperation(), - pdk.get_grule("active_diff", "active_tap")["min_separation"], - ) - tap_sep += pdk.get_grule(tap_layer, "active_tap")["min_enclosure"] - tap_encloses = ( - 2 * (tap_sep + interdigitized_fets.xmax), - 2 * (tap_sep + interdigitized_fets.ymax), - ) - tie_ref = top_level << tapring(pdk, enclosed_rectangle = tap_encloses, sdlayer = tap_layer, horizontal_glayer = tie_layers[0], vertical_glayer = tie_layers[1]) - top_level.add_ports(tie_ref.get_ports_list(), prefix="welltie_") - try: - top_level << straight_route(pdk, top_level.ports[f"fet_B_{numcols - 1}_dummy_R_gsdcon_top_met_E"],top_level.ports["welltie_E_top_met_E"],glayer2="met1") - top_level << straight_route(pdk, top_level.ports["fet_A_0_dummy_L_gsdcon_top_met_W"],top_level.ports["welltie_W_top_met_W"],glayer2="met1") - except KeyError: - pass - try: - end_col = numcols - 1 - port1 = f'B_{end_col}_dummy_R_gdscon_top_met_E' - top_level << straight_route(pdk, top_level.ports[port1], top_level.ports["welltie_E_top_met_E"], glayer2="met1") - except KeyError: - pass - + raise ValueError(f"device must be either 'nmos' or 'pmos', got {device}") + + viam2m3 = via_stack(pdk, "met2", "met3", centered=True) + metal_min_dim = max(pdk.get_grule("met2")["min_width"], pdk.get_grule("met3")["min_width"]) + metal_space = max(pdk.get_grule("met2")["min_separation"], pdk.get_grule("met3")["min_separation"], metal_min_dim) + gate_route_os = (evaluate_bbox(viam2m3)[0] - fetL.ports["multiplier_0_gate_W"].width + metal_space) + min_spacing_y = metal_space + 2 * gate_route_os + min_spacing_y -= 2 * abs(fetL.ports["well_S"].center[1] - fetL.ports["multiplier_0_gate_S"].center[1]) + ref_top = (cmirror << fetL).movey(fetL.ymax + min_spacing_y / 2).movex(-fetL.xmax) + # Output device (bottom, mirrored) + out_bot = (cmirror << fetR) + out_bot.mirror_y() + out_bot.movey(-fetR.ymax - min_spacing_y / 2).movex(-fetR.xmax) + cmirror.add_ports(ref_top.get_ports_list(), prefix="ref_") + cmirror.add_ports(out_bot.get_ports_list(), prefix="out_") + if with_substrate_tap: + tapref = cmirror << tapring(pdk, evaluate_bbox(cmirror, padding=1), horizontal_glayer="met1") + cmirror.add_ports(tapref.get_ports_list(), prefix="tap_") + + for inst in [ref_top, out_bot]: + try: + cmirror << straight_route(pdk, inst.ports["multiplier_0_dummy_L_gsdcon_top_met_W"], cmirror.ports["tap_W_top_met_W"],glayer2="met1") + except KeyError: + pass + source_bar = cmirror << c_route(pdk, ref_top.ports["multiplier_0_source_E"], out_bot.ports["multiplier_0_source_E"], viaoffset=False) + cmirror.add_ports(source_bar.get_ports_list(), prefix="VSS_") + gate_bar = cmirror << c_route(pdk, ref_top.ports["multiplier_0_gate_W"], out_bot.ports["multiplier_0_gate_W"], viaoffset=False) + ref_drain = (ref_top.ports.get("multiplier_0_drain_W") or ref_top.ports.get("multiplier_0_drain_E")) + cmirror << L_route(pdk, ref_drain, gate_bar.ports["con_N"], viaoffset=False, fullbottom=False) + cmirror.add_ports([out_bot.ports["multiplier_0_drain_N"]], prefix="VOUT_") + # add a pwell if device.lower() in ['nmos','nfet']: - top_level.add_padding(layers = (pdk.get_glayer("pwell"),), default = pdk.get_grule("pwell", "active_tap")["min_enclosure"], ) - top_level = add_ports_perimeter(top_level, layer = pdk.get_glayer("pwell"), prefix="well_") + cmirror.add_padding(layers = (pdk.get_glayer("pwell"),), default = pdk.get_grule("pwell", "active_tap")["min_enclosure"], ) + cmirror = add_ports_perimeter(cmirror, layer = pdk.get_glayer("pwell"), prefix="well_") elif device.lower() in ['pmos','pfet']: - top_level.add_padding(layers = (pdk.get_glayer("nwell"),), default = pdk.get_grule("nwell", "active_tap")["min_enclosure"], ) - top_level = add_ports_perimeter(top_level, layer = pdk.get_glayer("nwell"), prefix="well_") + cmirror.add_padding(layers = (pdk.get_glayer("nwell"),), default = pdk.get_grule("nwell", "active_tap")["min_enclosure"], ) + cmirror = add_ports_perimeter(cmirror, layer = pdk.get_glayer("nwell"), prefix="well_") else: raise ValueError(f"Device type {device} not recognized. Use 'nfet' or 'pfet'.") + cmirror.info['netlist'] = current_mirror_netlist(fetL, fetR) + return cmirror - - # add the substrate tap if specified - if with_substrate_tap: - subtap_sep = pdk.get_grule("dnwell", "active_tap")["min_separation"] - subtap_enclosure = ( - 2.5 * (subtap_sep + interdigitized_fets.xmax), - 2.5 * (subtap_sep + interdigitized_fets.ymax), - ) - subtap_ring = top_level << tapring(pdk, enclosed_rectangle = subtap_enclosure, sdlayer = "p+s/d", horizontal_glayer = "met2", vertical_glayer = "met1") - top_level.add_ports(subtap_ring.get_ports_list(), prefix="substrate_tap_") - - top_level.add_ports(source_short.get_ports_list(), prefix='purposegndports') - - - top_level.info['netlist'] = current_mirror_netlist( - pdk, - width=kwargs.get('width', 3), length=kwargs.get('length'), multipliers=numcols, with_dummy=with_dummy, - n_or_p_fet = 'nfet' if device.lower() in ['nmos', 'nfet'] else 'pfet' if device.lower() in ['pmos', 'pfet'] else (_ for _ in ()).throw(ValueError(f"Device type {device} not recognized. Use 'nfet' or 'pfet'.")), - subckt_only=subckt_only - ) - return top_level - +import time if __name__ == "__main__": - comp = current_mirror(sky130) + comp =current_mirror(sky130) # comp.pprint_ports() - comp = add_cm_labels(comp,sky130) + comp =add_cm_labels(comp,sky130) comp.name = "CM" - comp.show() #print(comp.info['netlist'].generate_netlist()) print("...Running DRC...") drc_result = sky130.drc_magic(comp, "CM") + print(drc_result) ## Klayout DRC #drc_result = sky130.drc(comp)\n - time.sleep(5) - print("...Running LVS...") - lvs_res=sky130.lvs_netgen(comp, "CM") + lvs_res=sky130.lvs_netgen(comp, "CM", copy_intermediate_files=True, show_scripts=True) + print(lvs_res) #print("...Saving GDS...") - #comp.write_gds('out_CMirror.gds') + #comp.write_gds('out_current_mirror,.gds')