From 0e978940b09f7776934173f1b10b607210730607 Mon Sep 17 00:00:00 2001 From: Aditya Date: Fri, 21 Nov 2025 15:07:47 +0000 Subject: [PATCH 1/4] Added JSON since it is better for structured data(like nodes and various parameters). The previous version failed since it likely stored everything as string. This change can successfully run on the generate_sample files. --- .../current_mirror.py | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 src/glayout/blocks/elementary/current_mirror_openfasoc/current_mirror.py diff --git a/src/glayout/blocks/elementary/current_mirror_openfasoc/current_mirror.py b/src/glayout/blocks/elementary/current_mirror_openfasoc/current_mirror.py new file mode 100644 index 00000000..4f6044c4 --- /dev/null +++ b/src/glayout/blocks/elementary/current_mirror_openfasoc/current_mirror.py @@ -0,0 +1,237 @@ +from glayout.flow.placement.two_transistor_interdigitized import two_nfet_interdigitized, two_pfet_interdigitized +from glayout.flow.pdk.mappedpdk import MappedPDK +from glayout.flow.routing.c_route import c_route +from glayout.flow.routing.L_route import L_route +from glayout.flow.routing.straight_route import straight_route +from glayout.flow.spice.netlist import Netlist +from glayout.flow.pdk.sky130_mapped import sky130_mapped_pdk as sky130 +from glayout.flow.primitives.fet import nmos, pmos +from glayout.flow.primitives.guardring import tapring +from glayout.flow.pdk.util.port_utils import add_ports_perimeter,rename_ports_by_orientation +from gdsfactory.component import Component +from gdsfactory.cell import cell +from glayout.flow.pdk.util.comp_utils import evaluate_bbox, prec_center, prec_ref_center, align_comp_to_port +from typing import Optional, Union +from glayout.flow.pdk.sky130_mapped import sky130_mapped_pdk +from glayout.flow.primitives.via_gen import via_stack +from gdsfactory.components import text_freetype, rectangle + +try: + from evaluator_wrapper import run_evaluation +except ImportError: + print("Warning: evaluator_wrapper not found. Evaluation will be skipped.") + run_evaluation = None + +def add_cm_labels(cm_in: Component, + pdk: MappedPDK + ) -> Component: + + cm_in.unlock() + met2_pin = (68,16) + met2_label = (68,5) + + # 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() + 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() + 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() + 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() + 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, + length: float, + multipliers: int, + with_dummy: bool = True, + n_or_p_fet: Optional[str] = 'nfet', + subckt_only: Optional[bool] = False +) -> 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 + } + ) + + +#@cell +def current_mirror( + pdk: MappedPDK, + numcols: int = 3, + device: str = 'nfet', + with_dummy: Optional[bool] = True, + with_substrate_tap: Optional[bool] = False, + with_tie: Optional[bool] = True, + tie_layers: tuple[str,str]=("met2","met1"), + **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 + + 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 + + Returns: + Component: a current mirror component object + """ + top_level = Component("current mirror") + if device in ['nmos', 'nfet']: + interdigitized_fets = two_nfet_interdigitized( + pdk, + numcols=numcols, + dummy=with_dummy, + with_substrate_tap=False, + with_tie=False, + **kwargs + ) + elif device in ['pmos', 'pfet']: + interdigitized_fets = two_pfet_interdigitized( + pdk, + numcols=numcols, + dummy=with_dummy, + with_substrate_tap=False, + with_tie=False, + **kwargs + ) + 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 in ['nmos','nfet']: + tap_layer = "p+s/d" + if device in ['pmos','pfet']: + tap_layer = "n+s/d" + tap_sep = max( + float(pdk.util_max_metal_seperation()), + float(pdk.get_grule("active_diff", "active_tap")["min_separation"]), + ) + tap_sep += float(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 + + # add a pwell + if device 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_") + if device 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_") + + + # 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') + + + current_mirror_obj= current_mirror_netlist( + pdk, + width=kwargs.get('width', 3), + length=kwargs.get('length', 0.15), + multipliers=numcols, + with_dummy=with_dummy, + n_or_p_fet=device, + subckt_only=True + ) + + top_level.info['netlist'] = current_mirror_obj.generate_netlist() + + import json + top_level.info['netlist_dict'] = json.dumps({ + 'circuit_name': current_mirror_obj.circuit_name, + 'nodes': current_mirror_obj.nodes, + 'source_netlist': current_mirror_obj.source_netlist, + 'instance_format': current_mirror_obj.instance_format, + 'parameters':current_mirror_obj.parameters if hasattr(current_mirror_obj, 'parameters') else {} + }) + + return top_level + +if __name__=="__main__": + current_mirror = add_cm_labels(current_mirror(sky130_mapped_pdk, device='pfet'),sky130_mapped_pdk) + #current_mirror.show() + current_mirror.name = "CMIRROR" + #magic_drc_result = sky130_mapped_pdk.drc_magic(current_mirror, current_mirror.name) + #netgen_lvs_result = sky130_mapped_pdk.lvs_netgen(current_mirror, current_mirror.name) + current_mirror_gds = current_mirror.write_gds("current_mirror.gds") + res = run_evaluation("current_mirror.gds", current_mirror.name, current_mirror) \ No newline at end of file From bf4a5ae46733fe4b463552f1984286065145e505 Mon Sep 17 00:00:00 2001 From: Aditya Date: Fri, 21 Nov 2025 15:48:31 +0000 Subject: [PATCH 2/4] Changed so that if the netlist is a string, it can be structured into a JSON --- .../diff_pair_openfasoc/diff_pair.py | 370 ++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 src/glayout/blocks/elementary/diff_pair_openfasoc/diff_pair.py diff --git a/src/glayout/blocks/elementary/diff_pair_openfasoc/diff_pair.py b/src/glayout/blocks/elementary/diff_pair_openfasoc/diff_pair.py new file mode 100644 index 00000000..83bf2b39 --- /dev/null +++ b/src/glayout/blocks/elementary/diff_pair_openfasoc/diff_pair.py @@ -0,0 +1,370 @@ +from typing import Optional, Union + +from gdsfactory.cell import cell +from gdsfactory.component import Component, copy +from gdsfactory.components.rectangle import rectangle +from gdsfactory.routing.route_quad import route_quad +from gdsfactory.routing.route_sharp import route_sharp +from glayout.flow.pdk.mappedpdk import MappedPDK +from glayout.flow.pdk.util.comp_utils import align_comp_to_port, evaluate_bbox, movex, movey +from glayout.flow.pdk.util.port_utils import ( + add_ports_perimeter, + get_orientation, + print_ports, + rename_ports_by_list, + rename_ports_by_orientation, + set_port_orientation, +) +from glayout.flow.pdk.util.snap_to_grid import component_snap_to_grid +from glayout.flow.placement.common_centroid_ab_ba import common_centroid_ab_ba +from glayout.flow.primitives.fet import nmos, pmos +from glayout.flow.primitives.guardring import tapring +from glayout.flow.primitives.via_gen import via_stack +from glayout.flow.routing.c_route import c_route +from glayout.flow.routing.smart_route import smart_route +from glayout.flow.routing.straight_route import straight_route +from glayout.flow.spice import Netlist +from glayout.flow.pdk.sky130_mapped import sky130_mapped_pdk +from gdsfactory.components import text_freetype +try: + from evaluator_wrapper import run_evaluation +except ImportError: + print("Warning: evaluator_wrapper not found. Evaluation will be skipped.") + run_evaluation = None +import json + +def add_df_labels(df_in: Component, + pdk: MappedPDK + ) -> Component: + + df_in.unlock() + met1_pin = (67,16) + met1_label = (67,5) + met2_pin = (68,16) + met2_label = (68,5) + # list that will contain all port/comp info + move_info = list() + # create labels and append to info list + # vtail + vtaillabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + vtaillabel.add_label(text="VTAIL",layer=pdk.get_glayer("met2_label")) + move_info.append((vtaillabel,df_in.ports["bl_multiplier_0_source_S"],None)) + + # vdd1 + vdd1label = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + vdd1label.add_label(text="VDD1",layer=pdk.get_glayer("met2_label")) + move_info.append((vdd1label,df_in.ports["tl_multiplier_0_drain_N"],None)) + + # vdd2 + vdd2label = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + vdd2label.add_label(text="VDD2",layer=pdk.get_glayer("met2_label")) + move_info.append((vdd2label,df_in.ports["tr_multiplier_0_drain_N"],None)) + + # VB + vblabel = rectangle(layer=pdk.get_glayer("met1_pin"),size=(0.5,0.5),centered=True).copy() + vblabel.add_label(text="B",layer=pdk.get_glayer("met1_label")) + move_info.append((vblabel,df_in.ports["tap_N_top_met_S"], None)) + + # VP + vplabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + vplabel.add_label(text="VP",layer=pdk.get_glayer("met2_label")) + move_info.append((vplabel,df_in.ports["br_multiplier_0_gate_S"], None)) + + # VN + vnlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + vnlabel.add_label(text="VN",layer=pdk.get_glayer("met2_label")) + move_info.append((vnlabel,df_in.ports["bl_multiplier_0_gate_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) + df_in.add(compref) + return df_in.flatten() + +def diff_pair_netlist(fetL: Component, fetR: Component) -> Netlist: + """Create netlist for differential pair with robust error handling""" + diff_pair_netlist_obj = Netlist(circuit_name='DIFF_PAIR', nodes=['VP', 'VN', 'VDD1', 'VDD2', 'VTAIL', 'B']) + + def reconstruct_netlist_from_component(component, comp_name): + """Helper to safely extract and reconstruct netlist from component""" + try: + # Try to get the netlist from info + netlist = component.info.get('netlist', None) + + # If netlist is already a Netlist object, return it + if isinstance(netlist, Netlist): + return netlist + + # If netlist is a string, we need to reconstruct + if isinstance(netlist, str): + # Try to get netlist_data_json (this is what fet.py stores) + netlist_data_json = component.info.get('netlist_data_json', None) + + if netlist_data_json: + try: + data = json.loads(netlist_data_json) + reconstructed = Netlist( + circuit_name=data.get('circuit_name', 'unknown'), + nodes=data.get('nodes', ['D', 'G', 'S', 'B']) + ) + reconstructed.source_netlist = data.get('source_netlist', '') + if 'parameters' in data: + reconstructed.parameters = data['parameters'] + return reconstructed + except (json.JSONDecodeError, KeyError) as e: + print(f"Debug: JSON decode failed for {comp_name}: {e}") + + # Try netlist_data dict + netlist_data = component.info.get('netlist_data', None) + if netlist_data and isinstance(netlist_data, dict): + try: + reconstructed = Netlist( + circuit_name=netlist_data.get('circuit_name', 'unknown'), + nodes=netlist_data.get('nodes', ['D', 'G', 'S', 'B']) + ) + reconstructed.source_netlist = netlist_data.get('source_netlist', '') + if 'parameters' in netlist_data: + reconstructed.parameters = netlist_data['parameters'] + return reconstructed + except (KeyError, TypeError) as e: + print(f"Debug: netlist_data parse failed for {comp_name}: {e}") + + # If we get here, we couldn't reconstruct + print(f"Debug: Could not find netlist_data_json or netlist_data for {comp_name}") + print(f"Debug: component.info type: {type(component.info)}") + # Try to safely print available keys + try: + if hasattr(component.info, '__dict__'): + print(f"Debug: Info attributes: {list(component.info.__dict__.keys())}") + elif hasattr(component.info, 'keys'): + print(f"Debug: Info keys: {list(component.info.keys())}") + except: + print(f"Debug: Could not inspect Info object") + + raise ValueError(f"No netlist_data found for string netlist in {comp_name} component") + + # If netlist is None or something else + print(f"Debug: Unexpected netlist type for {comp_name}: {type(netlist)}") + raise ValueError(f"Invalid netlist for {comp_name}") + + except Exception as e: + print(f"Error reconstructing netlist for {comp_name}: {e}") + raise + + # Reconstruct netlists for both FETs + fetL_netlist = reconstruct_netlist_from_component(fetL, "fetL") + fetR_netlist = reconstruct_netlist_from_component(fetR, "fetR") + + # Connect the netlists + diff_pair_netlist_obj.connect_netlist( + fetL_netlist, + [('D', 'VDD1'), ('G', 'VP'), ('S', 'VTAIL'), ('B', 'B')] + ) + diff_pair_netlist_obj.connect_netlist( + fetR_netlist, + [('D', 'VDD2'), ('G', 'VN'), ('S', 'VTAIL'), ('B', 'B')] + ) + + return diff_pair_netlist_obj + +@cell +def diff_pair( + pdk: MappedPDK, + width: float = 3, + fingers: int = 4, + length: Optional[float] = None, + n_or_p_fet: bool = True, + plus_minus_seperation: float = 0, + rmult: int = 1, + dummy: Union[bool, tuple[bool, bool]] = True, + substrate_tap: bool=True +) -> Component: + """create a diffpair with 2 transistors placed in two rows with common centroid place. Sources are shorted + width = width of the transistors + fingers = number of fingers in the transistors (must be 2 or more) + length = length of the transistors, None or 0 means use min length + short_source = if true connects source of both transistors + n_or_p_fet = if true the diffpair is made of nfets else it is made of pfets + substrate_tap: if true place a tapring around the diffpair (connects on met1) + """ + # TODO: error checking + pdk.activate() + diffpair = Component() + # create transistors + well = None + if isinstance(dummy, bool): + dummy = (dummy, dummy) + if n_or_p_fet: + 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" + else: + 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" + + # CRITICAL FIX: Generate the netlist BEFORE adding components as references + # This ensures we have access to the original component's info dict + try: + diff_pair_netlist_obj = diff_pair_netlist(fetL, fetR) + print(f"✓ Successfully generated diff_pair netlist") + except Exception as e: + import traceback + print(f"⚠ Warning: Failed to generate diff_pair netlist") + print(f" Error: {e}") + traceback.print_exc() + + # Try to safely inspect info without using .keys() + try: + if hasattr(fetL.info, '__dict__'): + print(f" fetL.info attributes: {list(fetL.info.__dict__.keys())}") + netlist_data_json = fetL.info.get('netlist_data_json', None) + if netlist_data_json: + print(f" fetL has netlist_data_json (first 100 chars): {netlist_data_json[:100]}") + except Exception as debug_e: + print(f" Could not inspect fetL.info: {debug_e}") + + # Create a dummy netlist as fallback + diff_pair_netlist_obj = Netlist(circuit_name='DIFF_PAIR', nodes=['VP', 'VN', 'VDD1', 'VDD2', 'VTAIL', 'B']) + print(f" Created fallback netlist") + + # place transistors + 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 = min_spacing_y - 2*abs(fetL.ports["well_S"].center[1] - fetL.ports["multiplier_0_gate_S"].center[1]) + # TODO: fix spacing where you see +-0.5 + a_topl = (diffpair << fetL).movey(fetL.ymax+min_spacing_y/2+0.5).movex(0-fetL.xmax-min_spacing_x/2) + b_topr = (diffpair << fetR).movey(fetR.ymax+min_spacing_y/2+0.5).movex(fetL.xmax+min_spacing_x/2) + a_botr = (diffpair << fetR) + a_botr.mirror_y() + a_botr.movey(0-0.5-fetL.ymax-min_spacing_y/2).movex(fetL.xmax+min_spacing_x/2) + b_botl = (diffpair << fetL) + b_botl.mirror_y() + b_botl.movey(0-0.5-fetR.ymax-min_spacing_y/2).movex(0-fetL.xmax-min_spacing_x/2) + # if substrate tap place substrate tap + if substrate_tap: + tapref = diffpair << tapring(pdk,evaluate_bbox(diffpair,padding=1),horizontal_glayer="met1") + diffpair.add_ports(tapref.get_ports_list(),prefix="tap_") + try: + diffpair< Component: + diffpair = common_centroid_ab_ba(pdk,width,fingers,length,n_or_p_fet,rmult,dummy,substrate_tap) + diffpair << smart_route(pdk,diffpair.ports["A_source_E"],diffpair.ports["B_source_E"],diffpair, diffpair) + return diffpair + +if __name__=="__main__": + diff_pair = add_df_labels(diff_pair(sky130_mapped_pdk),sky130_mapped_pdk) + #diff_pair = diff_pair(sky130_mapped_pdk) + #diff_pair.show() + diff_pair.name = "DIFF_PAIR" + # magic_drc_result = sky130_mapped_pdk.drc_magic(diff_pair, diff_pair.name) + # netgen_lvs_result = sky130_mapped_pdk.lvs_netgen(diff_pair, diff_pair.name) + diff_pair_gds = diff_pair.write_gds("diff_pair.gds") + res = run_evaluation("diff_pair.gds", diff_pair.name, diff_pair) \ No newline at end of file From e7d272042768eb6325508d78649b5d021b9fbb79 Mon Sep 17 00:00:00 2001 From: Aditya Date: Fri, 21 Nov 2025 15:57:03 +0000 Subject: [PATCH 3/4] Added JSON since diff_pair.py and current_mirror.py both relies on fet --- src/glayout/primitives/fet_openfasoc/fet.py | 663 ++++++++++++++++++++ 1 file changed, 663 insertions(+) create mode 100644 src/glayout/primitives/fet_openfasoc/fet.py diff --git a/src/glayout/primitives/fet_openfasoc/fet.py b/src/glayout/primitives/fet_openfasoc/fet.py new file mode 100644 index 00000000..af5307a9 --- /dev/null +++ b/src/glayout/primitives/fet_openfasoc/fet.py @@ -0,0 +1,663 @@ +from gdsfactory.grid import grid +from gdsfactory.cell import cell +from gdsfactory.component import Component, copy +from gdsfactory.components.rectangle import rectangle +from glayout.flow.pdk.mappedpdk import MappedPDK +from typing import Optional, Union +from glayout.flow.primitives.via_gen import via_array, via_stack +from glayout.flow.primitives.guardring import tapring +from pydantic import validate_arguments +from glayout.flow.pdk.util.comp_utils import evaluate_bbox, to_float, to_decimal, prec_array, prec_center, prec_ref_center, movey, align_comp_to_port +from glayout.flow.pdk.util.port_utils import rename_ports_by_orientation, rename_ports_by_list, add_ports_perimeter, print_ports +from glayout.flow.routing.c_route import c_route +from glayout.flow.routing.L_route import L_route +from glayout.flow.pdk.util.snap_to_grid import component_snap_to_grid +from decimal import Decimal +from glayout.flow.routing.straight_route import straight_route +from glayout.flow.spice import Netlist +import json + +@validate_arguments +def __gen_fingers_macro(pdk: MappedPDK, rmult: int, fingers: int, length: float, width: float, poly_height: float, sdlayer: str, inter_finger_topmet: str) -> Component: + """internal use: returns an array of fingers""" + length = pdk.snap_to_2xgrid(length) + width = pdk.snap_to_2xgrid(width) + poly_height = pdk.snap_to_2xgrid(poly_height) + sizing_ref_viastack = via_stack(pdk, "active_diff", "met1") + # figure out poly (gate) spacing: s/d metal doesnt overlap transistor, s/d min seperation criteria is met + sd_viaxdim = rmult*evaluate_bbox(via_stack(pdk, "active_diff", "met1"))[0] + poly_spacing = 2 * pdk.get_grule("poly", "mcon")["min_separation"] + pdk.get_grule("mcon")["width"] + poly_spacing = max(sd_viaxdim, poly_spacing) + met1_minsep = pdk.get_grule("met1")["min_separation"] + poly_spacing += met1_minsep if length < met1_minsep else 0 + # create a single finger + finger = Component("finger") + gate = finger << rectangle(size=(length, poly_height), layer=pdk.get_glayer("poly"), centered=True) + sd_viaarr = via_array(pdk, "active_diff", "met1", size=(sd_viaxdim, width), minus1=True, lay_bottom=False).copy() + interfinger_correction = via_array(pdk,"met1",inter_finger_topmet, size=(None, width),lay_every_layer=True, num_vias=(1,None)) + sd_viaarr << interfinger_correction + sd_viaarr_ref = finger << sd_viaarr + sd_viaarr_ref.movex((poly_spacing+length) / 2) + finger.add_ports(gate.get_ports_list(),prefix="gate_") + finger.add_ports(sd_viaarr_ref.get_ports_list(),prefix="rightsd_") + # create finger array + fingerarray = prec_array(finger, columns=fingers, rows=1, spacing=(poly_spacing+length, 1),absolute_spacing=True) + sd_via_ref_left = fingerarray << sd_viaarr + sd_via_ref_left.movex(0-(poly_spacing+length)/2) + fingerarray.add_ports(sd_via_ref_left.get_ports_list(),prefix="leftsd_") + # center finger array and add ports + centered_farray = Component() + fingerarray_ref_center = prec_ref_center(fingerarray) + centered_farray.add(fingerarray_ref_center) + centered_farray.add_ports(fingerarray_ref_center.get_ports_list()) + # create diffusion and +doped region + multiplier = rename_ports_by_orientation(centered_farray) + diff_extra_enc = 2 * pdk.get_grule("mcon", "active_diff")["min_enclosure"] + diff_dims =(diff_extra_enc + evaluate_bbox(multiplier)[0], width) + diff = multiplier << rectangle(size=diff_dims,layer=pdk.get_glayer("active_diff"),centered=True) + sd_diff_ovhg = pdk.get_grule(sdlayer, "active_diff")["min_enclosure"] + sdlayer_dims = [dim + 2*sd_diff_ovhg for dim in diff_dims] + sdlayer_ref = multiplier << rectangle(size=sdlayer_dims, layer=pdk.get_glayer(sdlayer),centered=True) + multiplier.add_ports(sdlayer_ref.get_ports_list(),prefix="plusdoped_") + multiplier.add_ports(diff.get_ports_list(),prefix="diff_") + return component_snap_to_grid(rename_ports_by_orientation(multiplier)) + +def fet_netlist( + pdk: MappedPDK, + circuit_name: str, + model: str, + width: float, + length: float, + fingers: int, + multipliers: int, + with_dummy: Union[bool, tuple[bool, bool]] +) -> Netlist: + # add spice netlist + num_dummies = 0 + if with_dummy == False or with_dummy == (False, False): + num_dummies = 0 + elif with_dummy == (True, False) or with_dummy == (False, True): + num_dummies = 1 + elif with_dummy == True or with_dummy == (True, True): + num_dummies = 2 + + if length is None: + length = pdk.get_grule('poly')['min_width'] + + ltop = length + wtop = width + mtop = fingers * multipliers + dmtop = multipliers + + source_netlist=""".subckt {circuit_name} {nodes} """+f'l={ltop} w={wtop} m={mtop} dm={dmtop} '+""" +XMAIN D G S B {model} l={{l}} w={{w}} m={{m}}""" + + for i in range(num_dummies): + source_netlist += "\nXDUMMY" + str(i+1) + " B B B B {model} l={{l}} w={{w}} m={{dm}}" + + source_netlist += "\n.ends {circuit_name}" + + return Netlist( + circuit_name=circuit_name, + nodes=['D', 'G', 'S', 'B'], + source_netlist=source_netlist, + instance_format="X{name} {nodes} {circuit_name} l={length} w={width} m={mult} dm={dummy_mult}", + parameters={ + 'model': model, + 'length': ltop, + 'width': wtop, + 'mult': mtop / 2, + 'dummy_mult': dmtop + } + ) + +# drain is above source +@cell +def multiplier( + pdk: MappedPDK, + sdlayer: str, + width: Optional[float] = 3, + length: Optional[float] = None, + fingers: int = 1, + routing: bool = True, + inter_finger_topmet: str = "met2", + dummy: Union[bool, tuple[bool, bool]] = True, + sd_route_topmet: str = "met2", + gate_route_topmet: str = "met2", + rmult: Optional[int]=None, + sd_rmult: int = 1, + gate_rmult: int=1, + interfinger_rmult: int=1, + sd_route_extension: float = 0, + gate_route_extension: float = 0, + dummy_routes: bool=True +) -> Component: + """Generic poly/sd vias generator + args: + pdk = pdk to use + sdlayer = either p+s/d for pmos or n+s/d for nmos + width = expands the transistor in the y direction + length = transitor length (if left None defaults to min length) + fingers = introduces additional fingers (sharing s/d) of width=width + routing = true or false, specfies if sd should be connected + inter_finger_topmet = top metal of the via array laid on the source/drain regions + ****NOTE: routing metal is layed over the source drain regions regardless of routing option + dummy = true or false add dummy active/plus doped regions + sd_rmult = multiplies thickness of sd metal (int only) + gate_rmult = multiplies gate by adding rows to the gate via array (int only) + interfinger_rmult = multiplies thickness of source/drain routes between the gates (int only) + sd_route_extension = float, how far extra to extend the source/drain connections (default=0) + gate_route_extension = float, how far extra to extend the gate connection (default=0) + dummy_routes: bool default=True, if true add add vias and short dummy poly,source,drain + + ports (one port for each edge), + ****NOTE: source is below drain: + gate_... all edges (top met route of gate connection) + source_...all edges (top met route of source connections) + drain_...all edges (top met route of drain connections) + plusdoped_...all edges (area of p+s/d or n+s/d layer) + diff_...all edges (diffusion region) + rowx_coly_...all ports associated with finger array include gate_... and array_ (array includes all ports of the viastacks in the array) + leftsd_...all ports associated with the left most via array + dummy_L,R_N,E,S,W ports if dummy_routes=True + """ + # error checking + if "+s/d" not in sdlayer: + raise ValueError("specify + doped region for multiplier") + if not "met" in sd_route_topmet or not "met" in gate_route_topmet: + raise ValueError("topmet specified must be metal layer") + if rmult: + if rmult<1: + raise ValueError("rmult must be positive int") + sd_rmult = rmult + gate_rmult = 1 + interfinger_rmult = ((rmult-1) or 1) + if sd_rmult<1 or interfinger_rmult<1 or gate_rmult<1: + raise ValueError("routing multipliers must be positive int") + if fingers < 1: + raise ValueError("number of fingers must be positive int") + # argument parsing and rule setup + min_length = pdk.get_grule("poly")["min_width"] + length = min_length if (length or min_length) <= min_length else length + length = pdk.snap_to_2xgrid(length) + min_width = max(min_length, pdk.get_grule("active_diff")["min_width"]) + width = min_width if (width or min_width) <= min_width else width + width = pdk.snap_to_2xgrid(width) + poly_height = width + 2 * pdk.get_grule("poly", "active_diff")["overhang"] + # call finger array + multiplier = __gen_fingers_macro(pdk, interfinger_rmult, fingers, length, width, poly_height, sdlayer, inter_finger_topmet) + # route all drains/ gates/ sources + if routing: + # place vias, then straight route from top port to via-botmet_N + sd_N_port = multiplier.ports["leftsd_top_met_N"] + sdvia = via_stack(pdk, "met1", sd_route_topmet) + sdmet_hieght = sd_rmult*evaluate_bbox(sdvia)[1] + sdroute_minsep = pdk.get_grule(sd_route_topmet)["min_separation"] + sdvia_ports = list() + for finger in range(fingers+1): + diff_top_port = movey(sd_N_port,destination=width/2) + # place sdvia such that metal does not overlap diffusion + big_extension = sdroute_minsep + sdmet_hieght/2 + sdmet_hieght + sdvia_extension = big_extension if finger % 2 else sdmet_hieght/2 + sdvia_ref = align_comp_to_port(sdvia,diff_top_port,alignment=('c','t')) + multiplier.add(sdvia_ref.movey(sdvia_extension + pdk.snap_to_2xgrid(sd_route_extension))) + multiplier << straight_route(pdk, diff_top_port, sdvia_ref.ports["bottom_met_N"]) + sdvia_ports += [sdvia_ref.ports["top_met_W"], sdvia_ref.ports["top_met_E"]] + # get the next port (break before this if last iteration because port D.N.E. and num gates=fingers) + if finger==fingers: + break + sd_N_port = multiplier.ports[f"row0_col{finger}_rightsd_top_met_N"] + # route gates + gate_S_port = multiplier.ports[f"row0_col{finger}_gate_S"] + metal_seperation = pdk.util_max_metal_seperation() + psuedo_Ngateroute = movey(gate_S_port.copy(),0-metal_seperation-gate_route_extension) + psuedo_Ngateroute.y = pdk.snap_to_2xgrid(psuedo_Ngateroute.y) + multiplier << straight_route(pdk,gate_S_port,psuedo_Ngateroute) + # place route met: gate + gate_width = gate_S_port.center[0] - multiplier.ports["row0_col0_gate_S"].center[0] + gate_S_port.width + gate = rename_ports_by_list(via_array(pdk,"poly",gate_route_topmet, size=(gate_width,None),num_vias=(None,gate_rmult), no_exception=True, fullbottom=True),[("top_met_","gate_")]) + gate_ref = align_comp_to_port(gate.copy(), psuedo_Ngateroute, alignment=(None,'b'),layer=pdk.get_glayer("poly")) + multiplier.add(gate_ref) + # place route met: source, drain + sd_width = sdvia_ports[-1].center[0] - sdvia_ports[0].center[0] + sd_route = rectangle(size=(sd_width,sdmet_hieght),layer=pdk.get_glayer(sd_route_topmet),centered=True) + source = align_comp_to_port(sd_route.copy(), sdvia_ports[0], alignment=(None,'c')) + drain = align_comp_to_port(sd_route.copy(), sdvia_ports[2], alignment=(None,'c')) + multiplier.add(source) + multiplier.add(drain) + # add ports + multiplier.add_ports(drain.get_ports_list(), prefix="drain_") + multiplier.add_ports(source.get_ports_list(), prefix="source_") + multiplier.add_ports(gate_ref.get_ports_list(prefix="gate_")) + # create dummy regions + if isinstance(dummy, bool): + dummyl = dummyr = dummy + else: + dummyl, dummyr = dummy + if dummyl or dummyr: + dummy = __gen_fingers_macro(pdk,rmult=interfinger_rmult,fingers=1,length=length,width=width,poly_height=poly_height,sdlayer=sdlayer,inter_finger_topmet="met1") + dummyvia = dummy << via_stack(pdk,"poly","met1",fullbottom=True) + align_comp_to_port(dummyvia,dummy.ports["row0_col0_gate_S"],layer=pdk.get_glayer("poly")) + dummy << L_route(pdk,dummyvia.ports["top_met_W"],dummy.ports["leftsd_top_met_S"]) + dummy << L_route(pdk,dummyvia.ports["top_met_E"],dummy.ports["row0_col0_rightsd_top_met_S"]) + dummy.add_ports(dummyvia.get_ports_list(),prefix="gsdcon_") + dummy_space = pdk.get_grule(sdlayer)["min_separation"] + dummy.xmax + sides = list() + if dummyl: + sides.append((-1,"dummy_L_")) + if dummyr: + sides.append((1,"dummy_R_")) + for side, name in sides: + dummy_ref = multiplier << dummy + dummy_ref.movex(side * (dummy_space + multiplier.xmax)) + multiplier.add_ports(dummy_ref.get_ports_list(),prefix=name) + # ensure correct port names and return + return component_snap_to_grid(rename_ports_by_orientation(multiplier)) + + +@validate_arguments +def __mult_array_macro( + pdk: MappedPDK, + sdlayer: str, + width: Optional[float] = 3, + fingers: Optional[int] = 1, + multipliers: Optional[int] = 1, + routing: Optional[bool] = True, + dummy: Optional[Union[bool, tuple[bool, bool]]] = True, + length: Optional[float] = None, + sd_route_topmet: Optional[str] = "met2", + gate_route_topmet: Optional[str] = "met2", + sd_route_left: Optional[bool] = True, + sd_rmult: int = 1, + gate_rmult: int=1, + interfinger_rmult: int=1, + dummy_routes: bool=True +) -> Component: + """create a multiplier array with multiplier_0 at the bottom + The array is correctly centered + """ + # create multiplier array + pdk.activate() + # TODO: error checking + multiplier_arr = Component("temp multiplier array") + multiplier_comp = multiplier( + pdk, + sdlayer, + width=width, + fingers=fingers, + dummy=dummy, + routing=routing, + length=length, + sd_route_topmet=sd_route_topmet, + gate_route_topmet=gate_route_topmet, + sd_rmult=sd_rmult, + gate_rmult=gate_rmult, + interfinger_rmult=interfinger_rmult, + dummy_routes=dummy_routes + ) + _max_metal_seperation_ps = max([pdk.get_grule("met"+str(i))["min_separation"] for i in range(1,5)]) + multiplier_separation = ( + to_decimal(_max_metal_seperation_ps) + + evaluate_bbox(multiplier_comp, True)[1] + ) + for rownum in range(multipliers): + row_displacment = rownum * multiplier_separation - (multiplier_separation/2 * (multipliers-1)) + row_ref = multiplier_arr << multiplier_comp + row_ref.movey(to_float(row_displacment)) + multiplier_arr.add_ports( + row_ref.get_ports_list(), prefix="multiplier_" + str(rownum) + "_" + ) + # TODO: fix extension (both extension are broken. IDK src extension and drain extension IDK metal layer) + src_extension = to_decimal(0.6) + drain_extension = src_extension + 3*to_decimal(pdk.get_grule("met4")["min_separation"]) + sd_side = "W" if sd_route_left else "E" + gate_side = "E" if sd_route_left else "W" + if routing and multipliers > 1: + for rownum in range(multipliers-1): + thismult = "multiplier_" + str(rownum) + "_" + nextmult = "multiplier_" + str(rownum+1) + "_" + # route sources left + srcpfx = thismult + "source_" + this_src = multiplier_arr.ports[srcpfx+sd_side] + next_src = multiplier_arr.ports[nextmult + "source_"+sd_side] + src_ref = multiplier_arr << c_route(pdk, this_src, next_src, viaoffset=(True,False), extension=to_float(src_extension)) + multiplier_arr.add_ports(src_ref.get_ports_list(), prefix=srcpfx) + # route drains left + drainpfx = thismult + "drain_" + this_drain = multiplier_arr.ports[drainpfx+sd_side] + next_drain = multiplier_arr.ports[nextmult + "drain_"+sd_side] + drain_ref = multiplier_arr << c_route(pdk, this_drain, next_drain, viaoffset=(True,False), extension=to_float(drain_extension)) + multiplier_arr.add_ports(drain_ref.get_ports_list(), prefix=drainpfx) + # route gates right + gatepfx = thismult + "gate_" + this_gate = multiplier_arr.ports[gatepfx+gate_side] + next_gate = multiplier_arr.ports[nextmult + "gate_"+gate_side] + gate_ref = multiplier_arr << c_route(pdk, this_gate, next_gate, viaoffset=(True,False), extension=to_float(src_extension)) + multiplier_arr.add_ports(gate_ref.get_ports_list(), prefix=gatepfx) + multiplier_arr = component_snap_to_grid(rename_ports_by_orientation(multiplier_arr)) + # add port redirects for shortcut names (source,drain,gate N,E,S,W) + for pin in ["source","drain","gate"]: + for side in ["N","E","S","W"]: + aliasport = pin + "_" + side + actualport = "multiplier_0_" + aliasport + multiplier_arr.add_port(port=multiplier_arr.ports[actualport],name=aliasport) + # recenter + final_arr = Component() + marrref = final_arr << multiplier_arr + correctionxy = prec_center(marrref) + marrref.movex(correctionxy[0]).movey(correctionxy[1]) + final_arr.add_ports(marrref.get_ports_list()) + return component_snap_to_grid(rename_ports_by_orientation(final_arr)) + + +#@cell +def nmos( + pdk, + width: float = 3, + fingers: Optional[int] = 1, + multipliers: Optional[int] = 1, + with_tie: bool = True, + with_dummy: Union[bool, tuple[bool, bool]] = True, + with_dnwell: bool = True, + with_substrate_tap: bool = True, + length: Optional[float] = None, + sd_route_topmet: str = "met2", + gate_route_topmet: str = "met2", + sd_route_left: bool = True, + rmult: Optional[int] = None, + sd_rmult: int=1, + gate_rmult: int=1, + interfinger_rmult: int=1, + tie_layers: tuple[str,str] = ("met2","met1"), + substrate_tap_layers: tuple[str,str] = ("met2","met1"), + dummy_routes: bool=True +) -> Component: + """Generic NMOS generator + pdk: mapped pdk to use + width: expands the NMOS in the y direction + fingers: introduces additional fingers (sharing source/drain) of width=width + multipliers: number of multipliers (a multiplier is a row of fingers) + with_tie: true or false, specfies if a bulk tie is required + with_dummy: tuple(bool,bool) or bool specifying both sides dummy or neither side dummy + ****using the tuple option, you can specify a single side dummy such as true,false + with_dnwell: bool use dnwell (multi well) + with_substrate_tap: add substrate tap on the very outside perimeter of nmos + length: if None or below min_length will default to min_length + sd_route_topmet: specify top metal glayer for the source/drain route + gate_route_topmet: specify top metal glayer for the gate route + sd_route_left: specify if the source/drain inter-multiplier routes should be on the left side or right side (if false) + rmult: if not None overrides all other multiplier options to provide a simple routing multiplier (int only) + sd_rmult: mulitplies the thickness of the source drain route (int only) + gate_rmult: add additional via rows to the gate route via array (int only) + interfinger_rmult: multiplies the thickness of the metal routes between the fingers (int only) + tie_layers: tuple[str,str] specifying (horizontal glayer, vertical glayer) or well tie ring. default=("met2","met1") + substrate_tap_layers: tuple[str,str] specifying (horizontal glayer, vertical glayer) or substrate tap ring. default=("met2","met1") + dummy_routes: bool default=True, if true add add vias and short dummy poly,source,drain + """ + # TODO: glayer checks + pdk.activate() + nfet = Component() + if rmult: + if rmult<1: + raise ValueError("rmult must be positive int") + sd_rmult = rmult + gate_rmult = 1 + interfinger_rmult = ((rmult-1) or 1) + # create and add multipliers to nfet + multiplier_arr = __mult_array_macro( + pdk, + "n+s/d", + width, + fingers, + multipliers, + dummy=with_dummy, + length=length, + sd_route_topmet=sd_route_topmet, + gate_route_topmet=gate_route_topmet, + sd_route_left=sd_route_left, + sd_rmult=sd_rmult, + gate_rmult=gate_rmult, + interfinger_rmult=interfinger_rmult, + dummy_routes=dummy_routes + ) + multiplier_arr_ref = multiplier_arr.ref() + nfet.add(multiplier_arr_ref) + nfet.add_ports(multiplier_arr_ref.get_ports_list()) + # add tie if tie + if with_tie: + tap_separation = max( + pdk.util_max_metal_seperation(), + pdk.get_grule("active_diff", "active_tap")["min_separation"], + ) + tap_separation += pdk.get_grule("p+s/d", "active_tap")["min_enclosure"] + tap_encloses = ( + 2 * (tap_separation + nfet.xmax), + 2 * (tap_separation + nfet.ymax), + ) + tiering_ref = nfet << tapring( + pdk, + enclosed_rectangle=tap_encloses, + sdlayer="p+s/d", + horizontal_glayer=tie_layers[0], + vertical_glayer=tie_layers[1], + ) + nfet.add_ports(tiering_ref.get_ports_list(), prefix="tie_") + for row in range(multipliers): + for dummyside,tieside in [("L","W"),("R","E")]: + try: + nfet< Component: + """Generic PMOS generator + pdk: mapped pdk to use + width: expands the PMOS in the y direction + fingers: introduces additional fingers (sharing source/drain) of width=width + multipliers: number of multipliers (a multiplier is a row of fingers) + with_tie: true or false, specfies if a bulk tie is required + dnwell: bool use dnwell if True, or use nwell if False + with_dummy: tuple(bool,bool) or bool specifying both sides dummy or neither side dummy + ****using the tuple option, you can specify a single side dummy such as true,false + with_substrate_tap: add substrate tap on the very outside perimeter of pmos + length: if None or below min_length will default to min_length + sd_route_topmet: specify top metal glayer for the source/drain route + gate_route_topmet: specify top metal glayer for the gate route + sd_route_left: specify if the source/drain inter-multiplier routes should be on the left side or right side (if false) + rmult: if not None overrides all other multiplier options to provide a simple routing multiplier (int only) + sd_rmult: mulitplies the thickness of the source drain route (int only) + gate_rmult: add additional via rows to the gate route via array (int only) + interfinger_rmult: multiplies the thickness of the metal routes between the fingers (int only) + tie_layers: tuple[str,str] specifying (horizontal glayer, vertical glayer) or well tie ring. default=("met2","met1") + substrate_tap_layers: tuple[str,str] specifying (horizontal glayer, vertical glayer) or substrate tap ring. default=("met2","met1") + dummy_routes: bool default=True, if true add add vias and short dummy poly,source,drain + """ + # TODO: glayer checks + pdk.activate() + pfet = Component() + if rmult: + if rmult<1: + raise ValueError("rmult must be positive int") + sd_rmult = rmult + gate_rmult = 1 + interfinger_rmult = ((rmult-1) or 1) + # create and add multipliers to nfet + multiplier_arr = __mult_array_macro( + pdk, + "p+s/d", + width, + fingers, + multipliers, + dummy=with_dummy, + length=length, + sd_route_topmet=sd_route_topmet, + gate_route_topmet=gate_route_topmet, + sd_route_left=sd_route_left, + gate_rmult=gate_rmult, + interfinger_rmult=interfinger_rmult, + sd_rmult=sd_rmult, + dummy_routes=dummy_routes + ) + multiplier_arr_ref = multiplier_arr.ref() + pfet.add(multiplier_arr_ref) + pfet.add_ports(multiplier_arr_ref.get_ports_list()) + # 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 = ( + 2 * (tap_separation + pfet.xmax), + 2 * (tap_separation + pfet.ymax), + ) + tapring_ref = pfet << tapring( + pdk, + enclosed_rectangle=tap_encloses, + sdlayer="n+s/d", + horizontal_glayer=tie_layers[0], + vertical_glayer=tie_layers[1], + ) + pfet.add_ports(tapring_ref.get_ports_list(),prefix="tie_") + for row in range(multipliers): + for dummyside,tieside in [("L","W"),("R","E")]: + try: + pfet< Date: Fri, 19 Dec 2025 14:04:48 +0000 Subject: [PATCH 4/4] Consolidated current_mirror and diff_pair files --- .../current_mirror/current_mirror.py | 191 +++------ .../current_mirror.py | 237 ----------- .../blocks/elementary/diff_pair/diff_pair.py | 362 ++++++++--------- .../diff_pair_openfasoc/diff_pair.py | 370 ------------------ 4 files changed, 210 insertions(+), 950 deletions(-) delete mode 100644 src/glayout/blocks/elementary/current_mirror_openfasoc/current_mirror.py delete mode 100644 src/glayout/blocks/elementary/diff_pair_openfasoc/diff_pair.py diff --git a/src/glayout/blocks/elementary/current_mirror/current_mirror.py b/src/glayout/blocks/elementary/current_mirror/current_mirror.py index 1006de66..44c6b75f 100644 --- a/src/glayout/blocks/elementary/current_mirror/current_mirror.py +++ b/src/glayout/blocks/elementary/current_mirror/current_mirror.py @@ -1,15 +1,18 @@ -from glayout import MappedPDK, sky130,gf180 -from glayout.routing import c_route,L_route,straight_route -from glayout.spice.netlist import Netlist from glayout.placement.two_transistor_interdigitized import two_nfet_interdigitized, two_pfet_interdigitized +from glayout.pdk.mappedpdk import MappedPDK +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.spice.netlist import Netlist +from glayout.pdk.sky130_mapped import sky130_mapped_pdk as sky130 from glayout.primitives.fet import nmos, pmos from glayout.primitives.guardring import tapring from glayout.util.port_utils import add_ports_perimeter,rename_ports_by_orientation from gdsfactory.component import Component from gdsfactory.cell import cell from glayout.util.comp_utils import evaluate_bbox, prec_center, prec_ref_center, align_comp_to_port -from typing import Optional, Union +from typing import Optional, Union +from glayout.pdk.sky130_mapped import sky130_mapped_pdk from glayout.primitives.via_gen import via_stack from gdsfactory.components import text_freetype, rectangle @@ -19,36 +22,35 @@ print("Warning: evaluator_wrapper not found. Evaluation will be skipped.") run_evaluation = None -def sky130_add_cm_labels(cm_in: Component) -> Component: +def add_cm_labels(cm_in: Component, + pdk: MappedPDK + ) -> Component: cm_in.unlock() - - # define layers` - met1_pin = (68,16) - met1_label = (68,5) - met2_pin = (69,16) - met2_label = (69,5) + met2_pin = (68,16) + met2_label = (68,5) + # list that will contain all port/comp info move_info = list() # create labels and append to info list # vss - vsslabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy() - vsslabel.add_label(text="VSS",layer=met1_label) + 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=met1_pin,size=(0.27,0.27),centered=True).copy() - vreflabel.add_label(text="VREF",layer=met1_label) + 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=met1_pin,size=(0.27,0.27),centered=True).copy() - vcopylabel.add_label(text="VCOPY",layer=met1_label) + 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=met1_pin,size=(0.5,0.5),centered=True).copy() - vblabel.add_label(text="VB",layer=met1_label) + 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 @@ -63,7 +65,7 @@ def current_mirror_netlist( width: float, length: float, multipliers: int, - with_dummy: Optional[bool] = False, + with_dummy: bool = True, n_or_p_fet: Optional[str] = 'nfet', subckt_only: Optional[bool] = False ) -> Netlist: @@ -97,7 +99,7 @@ def current_mirror_netlist( ) -@cell +#@cell def current_mirror( pdk: MappedPDK, numcols: int = 3, @@ -158,10 +160,10 @@ def current_mirror( if device in ['pmos','pfet']: tap_layer = "n+s/d" tap_sep = max( - pdk.util_max_metal_seperation(), - pdk.get_grule("active_diff", "active_tap")["min_separation"], + float(pdk.util_max_metal_seperation()), + float(pdk.get_grule("active_diff", "active_tap")["min_separation"]), ) - tap_sep += pdk.get_grule(tap_layer, "active_tap")["min_enclosure"] + tap_sep += float(pdk.get_grule(tap_layer, "active_tap")["min_enclosure"]) tap_encloses = ( 2 * (tap_sep + interdigitized_fets.xmax), 2 * (tap_sep + interdigitized_fets.ymax), @@ -202,129 +204,34 @@ def current_mirror( top_level.add_ports(source_short.get_ports_list(), prefix='purposegndports') - top_level.info['netlist'] = current_mirror_netlist( + current_mirror_obj= current_mirror_netlist( pdk, - width=kwargs.get('width', 3), length=kwargs.get('length', 0.15), multipliers=numcols, with_dummy=with_dummy, + width=kwargs.get('width', 3), + length=kwargs.get('length', 0.15), + multipliers=numcols, + with_dummy=with_dummy, n_or_p_fet=device, subckt_only=True ) - - return top_level + top_level.info['netlist'] = current_mirror_obj.generate_netlist() -def sky130_add_current_mirror_labels(current_mirror_in: Component) -> Component: - """ - Add labels to current mirror component for simulation and testing - """ - current_mirror_in.unlock() - # define layers - met1_pin = (68,16) - met1_label = (68,5) - met2_pin = (69,16) - met2_label = (69,5) - # list that will contain all port/comp info - move_info = list() - - # Reference voltage (drain of reference transistor) - vref_label = rectangle(layer=met1_pin, size=(0.5,0.5), centered=True).copy() - vref_label.add_label(text="VREF", layer=met1_label) - - # Copy current output (drain of mirror transistor) - vcopy_label = rectangle(layer=met1_pin, size=(0.5,0.5), centered=True).copy() - vcopy_label.add_label(text="VCOPY", layer=met1_label) - - # Ground/VSS (source connections) - vss_label = rectangle(layer=met1_pin, size=(0.5,0.5), centered=True).copy() - vss_label.add_label(text="VSS", layer=met1_label) - - # Bulk/VB (bulk/body connections) - vb_label = rectangle(layer=met1_pin, size=(0.5,0.5), centered=True).copy() - vb_label.add_label(text="VB", layer=met1_label) - - # Try to find appropriate ports and add labels - try: - # Look for drain ports for VREF and VCOPY - ref_drain_ports = [p for p in current_mirror_in.ports.keys() if 'A_drain' in p and 'met' in p] - copy_drain_ports = [p for p in current_mirror_in.ports.keys() if 'B_drain' in p and 'met' in p] - source_ports = [p for p in current_mirror_in.ports.keys() if 'source' in p and 'met' in p] - bulk_ports = [p for p in current_mirror_in.ports.keys() if ('tie' in p or 'well' in p) and 'met' in p] - - if ref_drain_ports: - move_info.append((vref_label, current_mirror_in.ports[ref_drain_ports[0]], None)) - if copy_drain_ports: - move_info.append((vcopy_label, current_mirror_in.ports[copy_drain_ports[0]], None)) - if source_ports: - move_info.append((vss_label, current_mirror_in.ports[source_ports[0]], None)) - if bulk_ports: - move_info.append((vb_label, current_mirror_in.ports[bulk_ports[0]], None)) - - except (KeyError, IndexError): - # Fallback - just add labels at component center - print("Warning: Could not find specific ports for labels, using fallback positioning") - move_info = [ - (vref_label, None, None), - (vcopy_label, None, None), - (vss_label, None, None), - (vb_label, None, None) - ] - - # move everything to position - for comp, prt, alignment in move_info: - alignment = ('c','b') if alignment is None else alignment - if prt is not None: - compref = align_comp_to_port(comp, prt, alignment=alignment) - else: - compref = comp - current_mirror_in.add(compref) - - return current_mirror_in.flatten() - + import json + top_level.info['netlist_dict'] = json.dumps({ + 'circuit_name': current_mirror_obj.circuit_name, + 'nodes': current_mirror_obj.nodes, + 'source_netlist': current_mirror_obj.source_netlist, + 'instance_format': current_mirror_obj.instance_format, + 'parameters':current_mirror_obj.parameters if hasattr(current_mirror_obj, 'parameters') else {} + }) -# Create and evaluate a current mirror instance -if __name__ == "__main__": - # OLD EVAL CODE - # comp = current_mirror(sky130) - # # comp.pprint_ports() - # 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") - # ## Klayout DRC - # #drc_result = sky130.drc(comp)\n - - # time.sleep(5) - - # print("...Running LVS...") - # lvs_res=sky130.lvs_netgen(comp, "CM") - # #print("...Saving GDS...") - # #comp.write_gds('out_CMirror.gds') - - # NEW EVAL CODE - # Create current mirror with labels - cm = sky130_add_current_mirror_labels( - current_mirror( - pdk=sky130, - numcols=3, - device='nfet', - width=3, - length=1, - with_dummy=True, - with_tie=True - ) - ) - - # Show the layout - cm.show() - cm.name = "current_mirror" - - # Write GDS file - cm_gds = cm.write_gds("current_mirror.gds") + return top_level - # Run evaluation if available - if run_evaluation is not None: - result = run_evaluation("current_mirror.gds", cm.name, cm) - print(result) - else: - print("Evaluation skipped - evaluator_wrapper not available") +if __name__=="__main__": + current_mirror = add_cm_labels(current_mirror(sky130_mapped_pdk, device='pfet'),sky130_mapped_pdk) + #current_mirror.show() + current_mirror.name = "CMIRROR" + #magic_drc_result = sky130_mapped_pdk.drc_magic(current_mirror, current_mirror.name) + #netgen_lvs_result = sky130_mapped_pdk.lvs_netgen(current_mirror, current_mirror.name) + current_mirror_gds = current_mirror.write_gds("current_mirror.gds") + res = run_evaluation("current_mirror.gds", current_mirror.name, current_mirror) \ No newline at end of file diff --git a/src/glayout/blocks/elementary/current_mirror_openfasoc/current_mirror.py b/src/glayout/blocks/elementary/current_mirror_openfasoc/current_mirror.py deleted file mode 100644 index 4f6044c4..00000000 --- a/src/glayout/blocks/elementary/current_mirror_openfasoc/current_mirror.py +++ /dev/null @@ -1,237 +0,0 @@ -from glayout.flow.placement.two_transistor_interdigitized import two_nfet_interdigitized, two_pfet_interdigitized -from glayout.flow.pdk.mappedpdk import MappedPDK -from glayout.flow.routing.c_route import c_route -from glayout.flow.routing.L_route import L_route -from glayout.flow.routing.straight_route import straight_route -from glayout.flow.spice.netlist import Netlist -from glayout.flow.pdk.sky130_mapped import sky130_mapped_pdk as sky130 -from glayout.flow.primitives.fet import nmos, pmos -from glayout.flow.primitives.guardring import tapring -from glayout.flow.pdk.util.port_utils import add_ports_perimeter,rename_ports_by_orientation -from gdsfactory.component import Component -from gdsfactory.cell import cell -from glayout.flow.pdk.util.comp_utils import evaluate_bbox, prec_center, prec_ref_center, align_comp_to_port -from typing import Optional, Union -from glayout.flow.pdk.sky130_mapped import sky130_mapped_pdk -from glayout.flow.primitives.via_gen import via_stack -from gdsfactory.components import text_freetype, rectangle - -try: - from evaluator_wrapper import run_evaluation -except ImportError: - print("Warning: evaluator_wrapper not found. Evaluation will be skipped.") - run_evaluation = None - -def add_cm_labels(cm_in: Component, - pdk: MappedPDK - ) -> Component: - - cm_in.unlock() - met2_pin = (68,16) - met2_label = (68,5) - - # 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() - 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() - 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() - 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() - 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, - length: float, - multipliers: int, - with_dummy: bool = True, - n_or_p_fet: Optional[str] = 'nfet', - subckt_only: Optional[bool] = False -) -> 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 - } - ) - - -#@cell -def current_mirror( - pdk: MappedPDK, - numcols: int = 3, - device: str = 'nfet', - with_dummy: Optional[bool] = True, - with_substrate_tap: Optional[bool] = False, - with_tie: Optional[bool] = True, - tie_layers: tuple[str,str]=("met2","met1"), - **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 - - 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 - - Returns: - Component: a current mirror component object - """ - top_level = Component("current mirror") - if device in ['nmos', 'nfet']: - interdigitized_fets = two_nfet_interdigitized( - pdk, - numcols=numcols, - dummy=with_dummy, - with_substrate_tap=False, - with_tie=False, - **kwargs - ) - elif device in ['pmos', 'pfet']: - interdigitized_fets = two_pfet_interdigitized( - pdk, - numcols=numcols, - dummy=with_dummy, - with_substrate_tap=False, - with_tie=False, - **kwargs - ) - 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 in ['nmos','nfet']: - tap_layer = "p+s/d" - if device in ['pmos','pfet']: - tap_layer = "n+s/d" - tap_sep = max( - float(pdk.util_max_metal_seperation()), - float(pdk.get_grule("active_diff", "active_tap")["min_separation"]), - ) - tap_sep += float(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 - - # add a pwell - if device 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_") - if device 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_") - - - # 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') - - - current_mirror_obj= current_mirror_netlist( - pdk, - width=kwargs.get('width', 3), - length=kwargs.get('length', 0.15), - multipliers=numcols, - with_dummy=with_dummy, - n_or_p_fet=device, - subckt_only=True - ) - - top_level.info['netlist'] = current_mirror_obj.generate_netlist() - - import json - top_level.info['netlist_dict'] = json.dumps({ - 'circuit_name': current_mirror_obj.circuit_name, - 'nodes': current_mirror_obj.nodes, - 'source_netlist': current_mirror_obj.source_netlist, - 'instance_format': current_mirror_obj.instance_format, - 'parameters':current_mirror_obj.parameters if hasattr(current_mirror_obj, 'parameters') else {} - }) - - return top_level - -if __name__=="__main__": - current_mirror = add_cm_labels(current_mirror(sky130_mapped_pdk, device='pfet'),sky130_mapped_pdk) - #current_mirror.show() - current_mirror.name = "CMIRROR" - #magic_drc_result = sky130_mapped_pdk.drc_magic(current_mirror, current_mirror.name) - #netgen_lvs_result = sky130_mapped_pdk.lvs_netgen(current_mirror, current_mirror.name) - current_mirror_gds = current_mirror.write_gds("current_mirror.gds") - res = run_evaluation("current_mirror.gds", current_mirror.name, current_mirror) \ No newline at end of file diff --git a/src/glayout/blocks/elementary/diff_pair/diff_pair.py b/src/glayout/blocks/elementary/diff_pair/diff_pair.py index 26b63acd..6597318d 100644 --- a/src/glayout/blocks/elementary/diff_pair/diff_pair.py +++ b/src/glayout/blocks/elementary/diff_pair/diff_pair.py @@ -1,8 +1,7 @@ -from typing import Optional, Union from glayout import MappedPDK, sky130,gf180 +from typing import Optional, Union from glayout.spice.netlist import Netlist from glayout.routing import c_route,L_route,straight_route - from gdsfactory.cell import cell from gdsfactory.component import Component, copy from gdsfactory.components.rectangle import rectangle @@ -24,190 +23,150 @@ from glayout.primitives.guardring import tapring from glayout.primitives.via_gen import via_stack from glayout.routing.smart_route import smart_route -from glayout.spice import Netlist from glayout.pdk.sky130_mapped import sky130_mapped_pdk from gdsfactory.components import text_freetype + try: from evaluator_wrapper import run_evaluation except ImportError: print("Warning: evaluator_wrapper not found. Evaluation will be skipped.") run_evaluation = None +import json - -def sky130_add_df_labels(df_in: Component) -> Component: +def add_df_labels(df_in: Component, + pdk: MappedPDK + ) -> Component: - df_in.unlock() - - # define layers` - met1_pin = (68,16) - met1_label = (68,5) - li1_pin = (67,16) - li1_label = (67,5) + df_in.unlock() + met1_pin = (67,16) + met1_label = (67,5) + met2_pin = (68,16) + met2_label = (68,5) # list that will contain all port/comp info - move_info = list() + move_info = list() # create labels and append to info list # vtail - vtaillabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy() - vtaillabel.add_label(text="VTAIL",layer=met1_label) - move_info.append((vtaillabel,df_in.ports["bl_multiplier_0_source_S"],None)) + vtaillabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + vtaillabel.add_label(text="VTAIL",layer=pdk.get_glayer("met2_label")) + move_info.append((vtaillabel,df_in.ports["bl_multiplier_0_source_S"],None)) # vdd1 - vdd1label = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy() - vdd1label.add_label(text="VDD1",layer=met1_label) - move_info.append((vdd1label,df_in.ports["tl_multiplier_0_drain_N"],None)) + vdd1label = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + vdd1label.add_label(text="VDD1",layer=pdk.get_glayer("met2_label")) + move_info.append((vdd1label,df_in.ports["tl_multiplier_0_drain_N"],None)) # vdd2 - vdd2label = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy() - vdd2label.add_label(text="VDD2",layer=met1_label) - move_info.append((vdd2label,df_in.ports["tr_multiplier_0_drain_N"],None)) + vdd2label = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + vdd2label.add_label(text="VDD2",layer=pdk.get_glayer("met2_label")) + move_info.append((vdd2label,df_in.ports["tr_multiplier_0_drain_N"],None)) # VB - vblabel = rectangle(layer=li1_pin,size=(0.5,0.5),centered=True).copy() - vblabel.add_label(text="B",layer=li1_label) - move_info.append((vblabel,df_in.ports["tap_N_top_met_S"], None)) + vblabel = rectangle(layer=pdk.get_glayer("met1_pin"),size=(0.5,0.5),centered=True).copy() + vblabel.add_label(text="B",layer=pdk.get_glayer("met1_label")) + move_info.append((vblabel,df_in.ports["tap_N_top_met_S"], None)) # VP - vplabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy() - vplabel.add_label(text="VP",layer=met1_label) - move_info.append((vplabel,df_in.ports["br_multiplier_0_gate_S"], None)) + vplabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + vplabel.add_label(text="VP",layer=pdk.get_glayer("met2_label")) + move_info.append((vplabel,df_in.ports["br_multiplier_0_gate_S"], None)) # VN - vnlabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy() - vnlabel.add_label(text="VN",layer=met1_label) - move_info.append((vnlabel,df_in.ports["bl_multiplier_0_gate_S"], None)) + vnlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() + vnlabel.add_label(text="VN",layer=pdk.get_glayer("met2_label")) + move_info.append((vnlabel,df_in.ports["bl_multiplier_0_gate_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) - df_in.add(compref) - return df_in.flatten() + 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) + df_in.add(compref) + return df_in.flatten() def diff_pair_netlist(fetL: Component, fetR: Component) -> Netlist: - diff_pair_netlist = Netlist(circuit_name='DIFF_PAIR', nodes=['VP', 'VN', 'VDD1', 'VDD2', 'VTAIL', 'B']) + """Create netlist for differential pair with robust error handling""" + diff_pair_netlist_obj = Netlist(circuit_name='DIFF_PAIR', nodes=['VP', 'VN', 'VDD1', 'VDD2', 'VTAIL', 'B']) - # Handle fetL netlist - reconstruct if it's a string - fetL_netlist = fetL.info['netlist'] - if isinstance(fetL_netlist, str): - if 'netlist_data' in fetL.info: - data = fetL.info['netlist_data'] - fetL_netlist = Netlist(circuit_name=data['circuit_name'], nodes=data['nodes']) - fetL_netlist.source_netlist = data['source_netlist'] - if 'parameters' in data: - fetL_netlist.parameters = data['parameters'] - else: - raise ValueError("No netlist_data found for string netlist in fetL component.info") + def reconstruct_netlist_from_component(component, comp_name): + """Helper to safely extract and reconstruct netlist from component""" + try: + # Try to get the netlist from info + netlist = component.info.get('netlist', None) + + # If netlist is already a Netlist object, return it + if isinstance(netlist, Netlist): + return netlist + + # If netlist is a string, we need to reconstruct + if isinstance(netlist, str): + # Try to get netlist_data_json (this is what fet.py stores) + netlist_data_json = component.info.get('netlist_data_json', None) + + if netlist_data_json: + try: + data = json.loads(netlist_data_json) + reconstructed = Netlist( + circuit_name=data.get('circuit_name', 'unknown'), + nodes=data.get('nodes', ['D', 'G', 'S', 'B']) + ) + reconstructed.source_netlist = data.get('source_netlist', '') + if 'parameters' in data: + reconstructed.parameters = data['parameters'] + return reconstructed + except (json.JSONDecodeError, KeyError) as e: + print(f"Debug: JSON decode failed for {comp_name}: {e}") + + # Try netlist_data dict + netlist_data = component.info.get('netlist_data', None) + if netlist_data and isinstance(netlist_data, dict): + try: + reconstructed = Netlist( + circuit_name=netlist_data.get('circuit_name', 'unknown'), + nodes=netlist_data.get('nodes', ['D', 'G', 'S', 'B']) + ) + reconstructed.source_netlist = netlist_data.get('source_netlist', '') + if 'parameters' in netlist_data: + reconstructed.parameters = netlist_data['parameters'] + return reconstructed + except (KeyError, TypeError) as e: + print(f"Debug: netlist_data parse failed for {comp_name}: {e}") + + # If we get here, we couldn't reconstruct + print(f"Debug: Could not find netlist_data_json or netlist_data for {comp_name}") + print(f"Debug: component.info type: {type(component.info)}") + # Try to safely print available keys + try: + if hasattr(component.info, '__dict__'): + print(f"Debug: Info attributes: {list(component.info.__dict__.keys())}") + elif hasattr(component.info, 'keys'): + print(f"Debug: Info keys: {list(component.info.keys())}") + except: + print(f"Debug: Could not inspect Info object") + + raise ValueError(f"No netlist_data found for string netlist in {comp_name} component") + + # If netlist is None or something else + print(f"Debug: Unexpected netlist type for {comp_name}: {type(netlist)}") + raise ValueError(f"Invalid netlist for {comp_name}") + + except Exception as e: + print(f"Error reconstructing netlist for {comp_name}: {e}") + raise - # Handle fetR netlist - reconstruct if it's a string - fetR_netlist = fetR.info['netlist'] - if isinstance(fetR_netlist, str): - if 'netlist_data' in fetR.info: - data = fetR.info['netlist_data'] - fetR_netlist = Netlist(circuit_name=data['circuit_name'], nodes=data['nodes']) - fetR_netlist.source_netlist = data['source_netlist'] - if 'parameters' in data: - fetR_netlist.parameters = data['parameters'] - else: - raise ValueError("No netlist_data found for string netlist in fetR component.info") + # Reconstruct netlists for both FETs + fetL_netlist = reconstruct_netlist_from_component(fetL, "fetL") + fetR_netlist = reconstruct_netlist_from_component(fetR, "fetR") - diff_pair_netlist.connect_netlist( + # Connect the netlists + diff_pair_netlist_obj.connect_netlist( fetL_netlist, [('D', 'VDD1'), ('G', 'VP'), ('S', 'VTAIL'), ('B', 'B')] ) - diff_pair_netlist.connect_netlist( + diff_pair_netlist_obj.connect_netlist( fetR_netlist, [('D', 'VDD2'), ('G', 'VN'), ('S', 'VTAIL'), ('B', 'B')] ) - return diff_pair_netlist - -def sky130_add_diff_pair_labels(diff_pair_in: Component) -> Component: - """ - Add labels to differential pair component for simulation and testing - """ - diff_pair_in.unlock() - # define layers - met1_pin = (68,16) - met1_label = (68,5) - met2_pin = (69,16) - met2_label = (69,5) - # list that will contain all port/comp info - move_info = list() - - # Positive input (VP) - vp_label = rectangle(layer=met1_pin, size=(0.5,0.5), centered=True).copy() - vp_label.add_label(text="VP", layer=met1_label) - - # Negative input (VN) - vn_label = rectangle(layer=met1_pin, size=(0.5,0.5), centered=True).copy() - vn_label.add_label(text="VN", layer=met1_label) - - # Positive output drain (VDD1) - vdd1_label = rectangle(layer=met2_pin, size=(0.5,0.5), centered=True).copy() - vdd1_label.add_label(text="VDD1", layer=met2_label) - - # Negative output drain (VDD2) - vdd2_label = rectangle(layer=met2_pin, size=(0.5,0.5), centered=True).copy() - vdd2_label.add_label(text="VDD2", layer=met2_label) - - # Tail current (VTAIL) - vtail_label = rectangle(layer=met1_pin, size=(0.5,0.5), centered=True).copy() - vtail_label.add_label(text="VTAIL", layer=met1_label) - - # Bulk/Body (B) - b_label = rectangle(layer=met1_pin, size=(0.5,0.5), centered=True).copy() - b_label.add_label(text="B", layer=met1_label) - - # Try to find appropriate ports and add labels - try: - # Look for gate ports for VP and VN - plus_gate_ports = [p for p in diff_pair_in.ports.keys() if 'PLUSgate' in p and 'met' in p] - minus_gate_ports = [p for p in diff_pair_in.ports.keys() if 'MINUSgate' in p and 'met' in p] - - # Look for drain ports for VDD1 and VDD2 - drain_ports = [p for p in diff_pair_in.ports.keys() if 'drain_route' in p and 'met' in p] - - # Look for source ports for VTAIL - source_ports = [p for p in diff_pair_in.ports.keys() if 'source_route' in p and 'met' in p] - - # Look for bulk/tie ports - bulk_ports = [p for p in diff_pair_in.ports.keys() if ('tie' in p or 'well' in p or 'tap' in p) and 'met' in p] - - if plus_gate_ports: - move_info.append((vp_label, diff_pair_in.ports[plus_gate_ports[0]], None)) - if minus_gate_ports: - move_info.append((vn_label, diff_pair_in.ports[minus_gate_ports[0]], None)) - if len(drain_ports) >= 2: - move_info.append((vdd1_label, diff_pair_in.ports[drain_ports[0]], None)) - move_info.append((vdd2_label, diff_pair_in.ports[drain_ports[1]], None)) - elif len(drain_ports) == 1: - move_info.append((vdd1_label, diff_pair_in.ports[drain_ports[0]], None)) - if source_ports: - move_info.append((vtail_label, diff_pair_in.ports[source_ports[0]], None)) - if bulk_ports: - move_info.append((b_label, diff_pair_in.ports[bulk_ports[0]], None)) - - except (KeyError, IndexError): - # Fallback - just add labels at component center - print("Warning: Could not find specific ports for labels, using fallback positioning") - move_info = [ - (vp_label, None, None), - (vn_label, None, None), - (vdd1_label, None, None), - (vdd2_label, None, None), - (vtail_label, None, None), - (b_label, None, None) - ] - - # move everything to position - for comp, prt, alignment in move_info: - alignment = ('c','b') if alignment is None else alignment - if prt is not None: - compref = align_comp_to_port(comp, prt, alignment=alignment) - else: - compref = comp - diff_pair_in.add(compref) - - return diff_pair_in.flatten() + + return diff_pair_netlist_obj @cell def diff_pair( @@ -246,6 +205,32 @@ def diff_pair( 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" + + # CRITICAL FIX: Generate the netlist BEFORE adding components as references + # This ensures we have access to the original component's info dict + try: + diff_pair_netlist_obj = diff_pair_netlist(fetL, fetR) + print(f"✓ Successfully generated diff_pair netlist") + except Exception as e: + import traceback + print(f"⚠ Warning: Failed to generate diff_pair netlist") + print(f" Error: {e}") + traceback.print_exc() + + # Try to safely inspect info without using .keys() + try: + if hasattr(fetL.info, '__dict__'): + print(f" fetL.info attributes: {list(fetL.info.__dict__.keys())}") + netlist_data_json = fetL.info.get('netlist_data_json', None) + if netlist_data_json: + print(f" fetL has netlist_data_json (first 100 chars): {netlist_data_json[:100]}") + except Exception as debug_e: + print(f" Could not inspect fetL.info: {debug_e}") + + # Create a dummy netlist as fallback + diff_pair_netlist_obj = Netlist(circuit_name='DIFF_PAIR', nodes=['VP', 'VN', 'VDD1', 'VDD2', 'VTAIL', 'B']) + print(f" Created fallback netlist") + # place transistors viam2m3 = via_stack(pdk,"met2","met3",centered=True) metal_min_dim = max(pdk.get_grule("met2")["min_width"],pdk.get_grule("met3")["min_width"]) @@ -286,8 +271,8 @@ def diff_pair( diffpair << route_quad(a_topl.ports["multiplier_0_source_E"], b_topr.ports["multiplier_0_source_W"], layer=pdk.get_glayer("met2")) diffpair << route_quad(b_botl.ports["multiplier_0_source_E"], a_botr.ports["multiplier_0_source_W"], layer=pdk.get_glayer("met2")) sextension = b_topr.ports["well_E"].center[0] - b_topr.ports["multiplier_0_source_E"].center[0] - source_routeE = diffpair << c_route(pdk, b_topr.ports["multiplier_0_source_E"], a_botr.ports["multiplier_0_source_E"],extension=sextension) - source_routeW = diffpair << c_route(pdk, a_topl.ports["multiplier_0_source_W"], b_botl.ports["multiplier_0_source_W"],extension=sextension) + source_routeE = diffpair << c_route(pdk, b_topr.ports["multiplier_0_source_E"], a_botr.ports["multiplier_0_source_E"],extension=sextension, viaoffset=False) + source_routeW = diffpair << c_route(pdk, a_topl.ports["multiplier_0_source_W"], b_botl.ports["multiplier_0_source_W"],extension=sextension, viaoffset=False) # route drains # place via at the drain drain_br_via = diffpair << viam2m3 @@ -340,7 +325,20 @@ def diff_pair( component = component_snap_to_grid(rename_ports_by_orientation(diffpair)) - component.info['netlist'] = diff_pair_netlist(fetL, fetR) + # Generate the diff_pair netlist + #diff_pair_netlist_obj = diff_pair_netlist(fetL, fetR) + + # Store as string (same pattern as fet.py) + component.info['netlist'] = diff_pair_netlist_obj.generate_netlist() + + # Optionally store netlist_data as JSON for reconstruction + component.info['netlist_data_json'] = json.dumps({ + 'circuit_name': diff_pair_netlist_obj.circuit_name, + 'nodes': diff_pair_netlist_obj.nodes, + 'source_netlist': diff_pair_netlist_obj.source_netlist, + 'parameters': diff_pair_netlist_obj.parameters if hasattr(diff_pair_netlist_obj, 'parameters') else {} + }) + return component @@ -361,50 +359,12 @@ def diff_pair_generic( diffpair << smart_route(pdk,diffpair.ports["A_source_E"],diffpair.ports["B_source_E"],diffpair, diffpair) return diffpair - -# Create and evaluate a differential pair instance -if __name__ == "__main__": - # this is the old eval code - - # Create differential pair with labels - comp =diff_pair(sky130) - # comp.pprint_ports() - comp=sky130_add_df_labels(comp) - comp.name = "DiffPair" - comp.show() - #print(comp.info['netlist'].generate_netlist()) - print("...Running DRC...") - drc_result = sky130.drc_magic(comp, "DiffPair") - ## Klayout DRC - #drc_result = sky130.drc(comp)\n - time.sleep(5) - print("...Running LVS...") - lvs_res=sky130.lvs_netgen(comp, "DiffPair") - #print("...Saving GDS...") - #comp.write_gds('out_Diffpair.gds') - - # This is the new eval code - dp = sky130_add_diff_pair_labels( - diff_pair( - pdk=sky130, - width=3, - fingers=4, - length=None, - n_or_p_fet=True, - plus_minus_seperation=0, - rmult=1, - dummy=True, - substrate_tap=True - ) - ) - # Show the layout - dp.show() - dp.name = "diff_pair" - # Write GDS file - dp_gds = dp.write_gds("diff_pair.gds") - # Run evaluation if available - if run_evaluation is not None: - result = run_evaluation("diff_pair.gds", dp.name, dp) - print(result) - else: - print("Evaluation skipped - evaluator_wrapper not available") +if __name__=="__main__": + diff_pair = add_df_labels(diff_pair(sky130_mapped_pdk),sky130_mapped_pdk) + #diff_pair = diff_pair(sky130_mapped_pdk) + #diff_pair.show() + diff_pair.name = "DIFF_PAIR" + # magic_drc_result = sky130_mapped_pdk.drc_magic(diff_pair, diff_pair.name) + # netgen_lvs_result = sky130_mapped_pdk.lvs_netgen(diff_pair, diff_pair.name) + diff_pair_gds = diff_pair.write_gds("diff_pair.gds") + res = run_evaluation("diff_pair.gds", diff_pair.name, diff_pair) diff --git a/src/glayout/blocks/elementary/diff_pair_openfasoc/diff_pair.py b/src/glayout/blocks/elementary/diff_pair_openfasoc/diff_pair.py deleted file mode 100644 index 83bf2b39..00000000 --- a/src/glayout/blocks/elementary/diff_pair_openfasoc/diff_pair.py +++ /dev/null @@ -1,370 +0,0 @@ -from typing import Optional, Union - -from gdsfactory.cell import cell -from gdsfactory.component import Component, copy -from gdsfactory.components.rectangle import rectangle -from gdsfactory.routing.route_quad import route_quad -from gdsfactory.routing.route_sharp import route_sharp -from glayout.flow.pdk.mappedpdk import MappedPDK -from glayout.flow.pdk.util.comp_utils import align_comp_to_port, evaluate_bbox, movex, movey -from glayout.flow.pdk.util.port_utils import ( - add_ports_perimeter, - get_orientation, - print_ports, - rename_ports_by_list, - rename_ports_by_orientation, - set_port_orientation, -) -from glayout.flow.pdk.util.snap_to_grid import component_snap_to_grid -from glayout.flow.placement.common_centroid_ab_ba import common_centroid_ab_ba -from glayout.flow.primitives.fet import nmos, pmos -from glayout.flow.primitives.guardring import tapring -from glayout.flow.primitives.via_gen import via_stack -from glayout.flow.routing.c_route import c_route -from glayout.flow.routing.smart_route import smart_route -from glayout.flow.routing.straight_route import straight_route -from glayout.flow.spice import Netlist -from glayout.flow.pdk.sky130_mapped import sky130_mapped_pdk -from gdsfactory.components import text_freetype -try: - from evaluator_wrapper import run_evaluation -except ImportError: - print("Warning: evaluator_wrapper not found. Evaluation will be skipped.") - run_evaluation = None -import json - -def add_df_labels(df_in: Component, - pdk: MappedPDK - ) -> Component: - - df_in.unlock() - met1_pin = (67,16) - met1_label = (67,5) - met2_pin = (68,16) - met2_label = (68,5) - # list that will contain all port/comp info - move_info = list() - # create labels and append to info list - # vtail - vtaillabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() - vtaillabel.add_label(text="VTAIL",layer=pdk.get_glayer("met2_label")) - move_info.append((vtaillabel,df_in.ports["bl_multiplier_0_source_S"],None)) - - # vdd1 - vdd1label = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() - vdd1label.add_label(text="VDD1",layer=pdk.get_glayer("met2_label")) - move_info.append((vdd1label,df_in.ports["tl_multiplier_0_drain_N"],None)) - - # vdd2 - vdd2label = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() - vdd2label.add_label(text="VDD2",layer=pdk.get_glayer("met2_label")) - move_info.append((vdd2label,df_in.ports["tr_multiplier_0_drain_N"],None)) - - # VB - vblabel = rectangle(layer=pdk.get_glayer("met1_pin"),size=(0.5,0.5),centered=True).copy() - vblabel.add_label(text="B",layer=pdk.get_glayer("met1_label")) - move_info.append((vblabel,df_in.ports["tap_N_top_met_S"], None)) - - # VP - vplabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() - vplabel.add_label(text="VP",layer=pdk.get_glayer("met2_label")) - move_info.append((vplabel,df_in.ports["br_multiplier_0_gate_S"], None)) - - # VN - vnlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy() - vnlabel.add_label(text="VN",layer=pdk.get_glayer("met2_label")) - move_info.append((vnlabel,df_in.ports["bl_multiplier_0_gate_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) - df_in.add(compref) - return df_in.flatten() - -def diff_pair_netlist(fetL: Component, fetR: Component) -> Netlist: - """Create netlist for differential pair with robust error handling""" - diff_pair_netlist_obj = Netlist(circuit_name='DIFF_PAIR', nodes=['VP', 'VN', 'VDD1', 'VDD2', 'VTAIL', 'B']) - - def reconstruct_netlist_from_component(component, comp_name): - """Helper to safely extract and reconstruct netlist from component""" - try: - # Try to get the netlist from info - netlist = component.info.get('netlist', None) - - # If netlist is already a Netlist object, return it - if isinstance(netlist, Netlist): - return netlist - - # If netlist is a string, we need to reconstruct - if isinstance(netlist, str): - # Try to get netlist_data_json (this is what fet.py stores) - netlist_data_json = component.info.get('netlist_data_json', None) - - if netlist_data_json: - try: - data = json.loads(netlist_data_json) - reconstructed = Netlist( - circuit_name=data.get('circuit_name', 'unknown'), - nodes=data.get('nodes', ['D', 'G', 'S', 'B']) - ) - reconstructed.source_netlist = data.get('source_netlist', '') - if 'parameters' in data: - reconstructed.parameters = data['parameters'] - return reconstructed - except (json.JSONDecodeError, KeyError) as e: - print(f"Debug: JSON decode failed for {comp_name}: {e}") - - # Try netlist_data dict - netlist_data = component.info.get('netlist_data', None) - if netlist_data and isinstance(netlist_data, dict): - try: - reconstructed = Netlist( - circuit_name=netlist_data.get('circuit_name', 'unknown'), - nodes=netlist_data.get('nodes', ['D', 'G', 'S', 'B']) - ) - reconstructed.source_netlist = netlist_data.get('source_netlist', '') - if 'parameters' in netlist_data: - reconstructed.parameters = netlist_data['parameters'] - return reconstructed - except (KeyError, TypeError) as e: - print(f"Debug: netlist_data parse failed for {comp_name}: {e}") - - # If we get here, we couldn't reconstruct - print(f"Debug: Could not find netlist_data_json or netlist_data for {comp_name}") - print(f"Debug: component.info type: {type(component.info)}") - # Try to safely print available keys - try: - if hasattr(component.info, '__dict__'): - print(f"Debug: Info attributes: {list(component.info.__dict__.keys())}") - elif hasattr(component.info, 'keys'): - print(f"Debug: Info keys: {list(component.info.keys())}") - except: - print(f"Debug: Could not inspect Info object") - - raise ValueError(f"No netlist_data found for string netlist in {comp_name} component") - - # If netlist is None or something else - print(f"Debug: Unexpected netlist type for {comp_name}: {type(netlist)}") - raise ValueError(f"Invalid netlist for {comp_name}") - - except Exception as e: - print(f"Error reconstructing netlist for {comp_name}: {e}") - raise - - # Reconstruct netlists for both FETs - fetL_netlist = reconstruct_netlist_from_component(fetL, "fetL") - fetR_netlist = reconstruct_netlist_from_component(fetR, "fetR") - - # Connect the netlists - diff_pair_netlist_obj.connect_netlist( - fetL_netlist, - [('D', 'VDD1'), ('G', 'VP'), ('S', 'VTAIL'), ('B', 'B')] - ) - diff_pair_netlist_obj.connect_netlist( - fetR_netlist, - [('D', 'VDD2'), ('G', 'VN'), ('S', 'VTAIL'), ('B', 'B')] - ) - - return diff_pair_netlist_obj - -@cell -def diff_pair( - pdk: MappedPDK, - width: float = 3, - fingers: int = 4, - length: Optional[float] = None, - n_or_p_fet: bool = True, - plus_minus_seperation: float = 0, - rmult: int = 1, - dummy: Union[bool, tuple[bool, bool]] = True, - substrate_tap: bool=True -) -> Component: - """create a diffpair with 2 transistors placed in two rows with common centroid place. Sources are shorted - width = width of the transistors - fingers = number of fingers in the transistors (must be 2 or more) - length = length of the transistors, None or 0 means use min length - short_source = if true connects source of both transistors - n_or_p_fet = if true the diffpair is made of nfets else it is made of pfets - substrate_tap: if true place a tapring around the diffpair (connects on met1) - """ - # TODO: error checking - pdk.activate() - diffpair = Component() - # create transistors - well = None - if isinstance(dummy, bool): - dummy = (dummy, dummy) - if n_or_p_fet: - 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" - else: - 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" - - # CRITICAL FIX: Generate the netlist BEFORE adding components as references - # This ensures we have access to the original component's info dict - try: - diff_pair_netlist_obj = diff_pair_netlist(fetL, fetR) - print(f"✓ Successfully generated diff_pair netlist") - except Exception as e: - import traceback - print(f"⚠ Warning: Failed to generate diff_pair netlist") - print(f" Error: {e}") - traceback.print_exc() - - # Try to safely inspect info without using .keys() - try: - if hasattr(fetL.info, '__dict__'): - print(f" fetL.info attributes: {list(fetL.info.__dict__.keys())}") - netlist_data_json = fetL.info.get('netlist_data_json', None) - if netlist_data_json: - print(f" fetL has netlist_data_json (first 100 chars): {netlist_data_json[:100]}") - except Exception as debug_e: - print(f" Could not inspect fetL.info: {debug_e}") - - # Create a dummy netlist as fallback - diff_pair_netlist_obj = Netlist(circuit_name='DIFF_PAIR', nodes=['VP', 'VN', 'VDD1', 'VDD2', 'VTAIL', 'B']) - print(f" Created fallback netlist") - - # place transistors - 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 = min_spacing_y - 2*abs(fetL.ports["well_S"].center[1] - fetL.ports["multiplier_0_gate_S"].center[1]) - # TODO: fix spacing where you see +-0.5 - a_topl = (diffpair << fetL).movey(fetL.ymax+min_spacing_y/2+0.5).movex(0-fetL.xmax-min_spacing_x/2) - b_topr = (diffpair << fetR).movey(fetR.ymax+min_spacing_y/2+0.5).movex(fetL.xmax+min_spacing_x/2) - a_botr = (diffpair << fetR) - a_botr.mirror_y() - a_botr.movey(0-0.5-fetL.ymax-min_spacing_y/2).movex(fetL.xmax+min_spacing_x/2) - b_botl = (diffpair << fetL) - b_botl.mirror_y() - b_botl.movey(0-0.5-fetR.ymax-min_spacing_y/2).movex(0-fetL.xmax-min_spacing_x/2) - # if substrate tap place substrate tap - if substrate_tap: - tapref = diffpair << tapring(pdk,evaluate_bbox(diffpair,padding=1),horizontal_glayer="met1") - diffpair.add_ports(tapref.get_ports_list(),prefix="tap_") - try: - diffpair< Component: - diffpair = common_centroid_ab_ba(pdk,width,fingers,length,n_or_p_fet,rmult,dummy,substrate_tap) - diffpair << smart_route(pdk,diffpair.ports["A_source_E"],diffpair.ports["B_source_E"],diffpair, diffpair) - return diffpair - -if __name__=="__main__": - diff_pair = add_df_labels(diff_pair(sky130_mapped_pdk),sky130_mapped_pdk) - #diff_pair = diff_pair(sky130_mapped_pdk) - #diff_pair.show() - diff_pair.name = "DIFF_PAIR" - # magic_drc_result = sky130_mapped_pdk.drc_magic(diff_pair, diff_pair.name) - # netgen_lvs_result = sky130_mapped_pdk.lvs_netgen(diff_pair, diff_pair.name) - diff_pair_gds = diff_pair.write_gds("diff_pair.gds") - res = run_evaluation("diff_pair.gds", diff_pair.name, diff_pair) \ No newline at end of file