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/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/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<