diff --git a/src/glayout/primitives/fet.py b/src/glayout/primitives/fet.py index 541d36dd..5dc8e04d 100644 --- a/src/glayout/primitives/fet.py +++ b/src/glayout/primitives/fet.py @@ -7,7 +7,8 @@ from glayout.primitives.via_gen import via_array, via_stack from glayout.primitives.guardring import tapring from pydantic import validate_arguments -from glayout.util.comp_utils import evaluate_bbox, to_float, to_decimal, prec_array, prec_center, prec_ref_center, movey, align_comp_to_port +from glayout.util.pattern import check_pattern_level, check_pattern_size, get_cols_positions, transpose_pattern +from glayout.util.comp_utils import evaluate_bbox, to_float, to_decimal, prec_array, prec_center, prec_ref_center, movey, align_comp_to_port, move from glayout.util.port_utils import rename_ports_by_orientation, rename_ports_by_list, add_ports_perimeter, print_ports from glayout.routing.c_route import c_route from glayout.routing.L_route import L_route @@ -130,7 +131,8 @@ def multiplier( interfinger_rmult: int=1, sd_route_extension: float = 0, gate_route_extension: float = 0, - dummy_routes: bool=True + dummy_routes: bool=True, + dummy_separation_rmult: int = 0 ) -> Component: """Generic poly/sd vias generator args: @@ -229,6 +231,11 @@ def multiplier( 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_")) + + # get reference for dummy sep + rvia = via_stack(pdk, "met1", sd_route_topmet) + dummy_sep = dummy_separation_rmult*float(evaluate_bbox(rvia)[1]) + # create dummy regions if isinstance(dummy, bool): dummyl = dummyr = dummy @@ -241,7 +248,7 @@ def multiplier( 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 + dummy_space = pdk.get_grule(sdlayer)["min_separation"] + (dummy.xmax-dummy.xmin)/2 + dummy_sep sides = list() if dummyl: sides.append((-1,"dummy_L_")) @@ -268,14 +275,29 @@ def __mult_array_macro( sd_route_topmet: Optional[str] = "met2", gate_route_topmet: Optional[str] = "met2", sd_route_left: Optional[bool] = True, + gd_route_bottom: Optional[bool] = True, #not used in this function but required to pass validation sd_rmult: int = 1, gate_rmult: int=1, interfinger_rmult: int=1, - dummy_routes: bool=True + dummy_routes: bool=True, + pattern: Union[list[str], list[int], None] = None, + is_gate_shared: bool = False, + is_src_shared: bool = False, + centered: bool = True, + dummy_separation_rmult: int = 0 ) -> Component: """create a multiplier array with multiplier_0 at the bottom The array is correctly centered """ + + # check the validy of the pattern if exists + if pattern is not None: + if(len(pattern)!=multipliers): + raise ValueError("Not a valid pattern. Must have the same number of " + "elements as the multiplier") + + unique_elements=list(set(pattern)) + # create multiplier array pdk.activate() # TODO: error checking @@ -293,11 +315,14 @@ def __mult_array_macro( sd_rmult=sd_rmult, gate_rmult=gate_rmult, interfinger_rmult=interfinger_rmult, - dummy_routes=dummy_routes + dummy_routes=dummy_routes, + dummy_separation_rmult=dummy_separation_rmult ) - _max_metal_seperation_ps = max([pdk.get_grule("met"+str(i))["min_separation"] for i in range(1,5)]) + _max_metal_separation_ps = max([pdk.get_grule("met"+str(i))["min_separation"] for i in range(1,5)]) + min_diff_separation = max(pdk.get_grule("n+s/d")["min_separation"],pdk.get_grule("p+s/d")["min_separation"]) + routing_separation = max([ _max_metal_separation_ps, min_diff_separation]) multiplier_separation = ( - to_decimal(_max_metal_seperation_ps) + to_decimal(routing_separation) + evaluate_bbox(multiplier_comp, True)[1] ) for rownum in range(multipliers): @@ -309,47 +334,471 @@ def __mult_array_macro( ) # 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"]) + + # this is a change proposal to avoid hardcoding and arbitrary number for the distance + sample_source_port="multiplier_0_source_W" + sample_drain_port="multiplier_0_drain_W" + source_port_width= multiplier_arr[sample_source_port].width + drain_port_width = multiplier_arr[sample_drain_port].width + distance = source_port_width/2 + drain_port_width/2 + 2*pdk.get_grule("met4")["min_separation"] + + drain_extension = to_decimal(to_float(src_extension)+ distance ) + + if pattern is not None: + drain_pattern_distances = [distance*2*n for n in range(len(unique_elements))] + src_pattern_distances = drain_pattern_distances + gate_pattern_distances = [distance*n for n in range(len(unique_elements))] + + if is_gate_shared: + gate_pattern_distances = [0]*len(unique_elements) + if is_src_shared: + drain_pattern_distances = [distance*n for n in + range(len(unique_elements))] + src_pattern_distances = [0]*len(unique_elements) + + + drain_distances_by_element = dict(zip(unique_elements,drain_pattern_distances)) + src_distances_by_element = dict(zip(unique_elements,src_pattern_distances)) + gate_distances_by_element = dict(zip(unique_elements,gate_pattern_distances)) + 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) + + dimension_wo_routing = evaluate_bbox(multiplier_arr) + + if routing: + if pattern is None 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) + + elif pattern is not None: + for rownum in range(len(unique_elements)): + this_id_pfx = unique_elements[rownum] + "_" + + this_source_pfx = this_id_pfx + "source_" + this_drain_pfx = this_id_pfx + "drain_" + this_gate_pfx = this_id_pfx + "gate_" + + eglayer_plusone = "met" + str(int(sd_route_topmet[-1])+1) + bvia = via_stack(pdk, sd_route_topmet, eglayer_plusone) + width_routing = sd_rmult*evaluate_bbox(bvia)[1] + + ref_port_drain = multiplier_arr.ports["multiplier_0_drain_" + sd_side] + ref_port_source = multiplier_arr.ports["multiplier_0_source_" + sd_side] + ref_port_gate = multiplier_arr.ports["multiplier_0_gate_" + gate_side] + + nref_port_drain= move(ref_port_drain, + destination=(ref_port_drain.center[0] - + drain_distances_by_element[unique_elements[rownum]] + - to_float(drain_extension), + (multiplier_arr.ymax + + multiplier_arr.ymin)/2)) + + nref_port_source= move(ref_port_source, + destination=(ref_port_source.center[0] - + src_distances_by_element[unique_elements[rownum]] + - to_float(src_extension), + (multiplier_arr.ymax + + multiplier_arr.ymin)/2)) + + nref_port_gate= move(ref_port_drain, + destination=(ref_port_gate.center[0] + + gate_distances_by_element[unique_elements[rownum]] + + to_float(src_extension), + (multiplier_arr.ymax + + multiplier_arr.ymin)/2)) + + sample_route = rectangle(size=(width_routing, + dimension_wo_routing[1]), + layer=pdk.get_glayer(eglayer_plusone), + centered=True) + + drain_route_ref = align_comp_to_port(sample_route.copy(), + nref_port_drain, + alignment=("l",'c')) + multiplier_arr.add(drain_route_ref) + multiplier_arr.add_ports(drain_route_ref.get_ports_list(), + prefix=this_drain_pfx) + + if not is_src_shared or rownum<1: + source_route_ref = align_comp_to_port(sample_route.copy(), + nref_port_source, + alignment=("l",'c')) + multiplier_arr.add(source_route_ref) + if is_src_shared: + this_source_pfx= "source_" + multiplier_arr.add_ports(source_route_ref.get_ports_list(), + prefix=this_source_pfx) + + if not is_gate_shared or rownum<1: + gate_route_ref = align_comp_to_port(sample_route.copy(), + nref_port_gate, + alignment=("r",'c')) + multiplier_arr.add(gate_route_ref) + if is_gate_shared: + this_gate_pfx= "gate_" + multiplier_arr.add_ports(gate_route_ref.get_ports_list(), + prefix=this_gate_pfx) + + multiplier_arr = rename_ports_by_orientation(multiplier_arr) + + for rownum, pat in enumerate(pattern): + + thismult = "multiplier_" + str(rownum) + "_" + + drainpfx = thismult + "drain_" + this_drain = multiplier_arr.ports[drainpfx+sd_side] + + sourcepfx = thismult + "source_" + this_source = multiplier_arr.ports[sourcepfx+sd_side] + + gatepfx = thismult + "gate_" + this_gate = multiplier_arr.ports[gatepfx+gate_side] + + drain_route=multiplier_arr.ports[str(pat)+"_drain_"+gate_side] + + if not is_src_shared: + source_route=multiplier_arr.ports[str(pat)+"_source_"+gate_side] + else: + source_route=multiplier_arr.ports["source_"+gate_side] + + + if not is_gate_shared: + gate_route=multiplier_arr.ports[str(pat)+"_gate_"+sd_side] + else: + gate_route = multiplier_arr.ports["gate_"+sd_side] + + # creating the connections from each of the ports to the + # corresponding routes + + src_ref = multiplier_arr << straight_route(pdk, this_source, + source_route) + drain_ref = multiplier_arr << straight_route(pdk, this_drain, + drain_route) + gate_ref = multiplier_arr << straight_route(pdk, + this_gate, + gate_route) 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) + if pattern is None: + 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]) + if centered: + 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)) +@validate_arguments +def __mult_2dim_array_macro( + pdk: MappedPDK, + sdlayer: str, + width: Optional[float] = 3, + fingers: Optional[int] = 1, + multipliers: Union[tuple[int,int], int] = (1,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, + gd_route_bottom: Optional[bool] = True, + sd_rmult: int = 1, + gate_rmult: int=1, + interfinger_rmult: int=1, + dummy_routes: bool=True, + pattern: Union[list[list[str]], list[list[int]], list[str], list[int], None] = None, + is_gate_shared: bool = False, + is_src_shared: bool = False, +) -> Component: + """create a multiplier array with multiplier_0 at the bottom + The array is correctly centered + """ + + # check the validy of the pattern if exists + if check_pattern_level(pattern) != 2: + raise ValueError("Pattern level not valid for this function") + + # check multiplier quantity matches the pattern + if check_pattern_size(pattern) != multipliers: + raise ValueError("Multipliers size doesn't match pattern size") + + # create multiplier array + pdk.activate() + # TODO: error checking + multiplier_2dim_arr = Component("temp multiplier array") + + t_pattern = transpose_pattern(pattern) + + l_cols = [] + + max_width = 0 + for column_pattern in t_pattern: + multiplier_arr = __mult_array_macro( + pdk, + sdlayer, + width, + fingers, + len(column_pattern), + dummy=False, + 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, + pattern=column_pattern, + is_gate_shared=is_gate_shared, + is_src_shared=is_src_shared, + centered=False + ) + l_cols.append(multiplier_arr) + + + if float(evaluate_bbox(multiplier_arr)[0]) > float( max_width ): + max_width = evaluate_bbox(multiplier_arr)[0] + + # create a multiplier reference to extract the original width + multiplier_ref = multiplier( + pdk, + sdlayer, + width=width, + fingers=fingers, + dummy=False, + 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, + ) + + mult_width = evaluate_bbox(multiplier_ref)[0] + rvia = via_stack(pdk, "met1", sd_route_topmet) + unit_sep_width = evaluate_bbox(rvia)[0] + dummy_separation_rmult = int((max_width - mult_width)/unit_sep_width) + # create dummy regions + if isinstance(dummy, bool): + dummyl = dummyr = dummy + else: + dummyl, dummyr = dummy + + if dummyl: + dummy_L = __mult_array_macro( + pdk, + sdlayer, + width, + fingers, + len(t_pattern[0]), + dummy=(True,False), + 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, + pattern=t_pattern[0], + is_gate_shared=is_gate_shared, + is_src_shared=is_src_shared, + centered=False, + dummy_separation_rmult=dummy_separation_rmult + ) + l_cols[0]=dummy_L + + if dummyr: + dummy_R = __mult_array_macro( + pdk, + sdlayer, + width, + fingers, + len(t_pattern[-1]), + dummy=(False,True), + 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, + pattern=t_pattern[0], + is_gate_shared=is_gate_shared, + is_src_shared=is_src_shared, + centered=False, + dummy_separation_rmult=dummy_separation_rmult + ) + l_cols[-1]=dummy_R + + + _max_metal_separation_ps = max([pdk.get_grule("met"+str(i))["min_separation"] for i in range(1,5)]) + min_diff_separation = max(pdk.get_grule("n+s/d")["min_separation"],pdk.get_grule("p+s/d")["min_separation"]) + routing_separation = max([ _max_metal_separation_ps, min_diff_separation]) + multiplier_separation = to_decimal( + float(routing_separation) + max_width + ) + + for colnum in range(len(l_cols)): + col_displacment = colnum * multiplier_separation - (multiplier_separation/2 * (len(l_cols)-1)) + col_ref = multiplier_2dim_arr << l_cols[colnum] + col_ref.movex(to_float(col_displacment)) + multiplier_2dim_arr.add_ports( + col_ref.get_ports_list(), prefix="col_" + str(colnum) + "_" + ) + + # routing + # get position for unique elements from the pattern + element_positions = get_cols_positions(pattern) + print(element_positions) + + + # using the C route will use the port width to make the routing + # then we can check the width for it and add the required space accordingly + # we assume the width is the same in west as the east + + # Here we placed gate and drain to the same side (bottom or top) + sample_element = list(element_positions.keys())[0] + sample_col=element_positions[sample_element][0] + if not is_gate_shared: + sample_gate_port="col_"+ str(sample_col) +"_" + sample_element + "_gate_S" + else: + sample_gate_port="col_"+ str(sample_col) +"_gate_S" + + sample_drain_port=( "col_"+ str(sample_col)+"_" + sample_element + + "_drain_S" ) + gate_port_width= multiplier_2dim_arr[sample_gate_port].width + drain_port_width = multiplier_2dim_arr[sample_drain_port].width + gd_distance = gate_port_width/2 + drain_port_width/2 + 2*pdk.get_grule("met4")["min_separation"] + + print(sample_gate_port) + print(sample_drain_port) + print(gd_distance) + + gd_side = "S" if gd_route_bottom else "N" + src_side = "N" if gd_route_bottom else "S" + + pins = ["drain", "gate", "source"] + sides = [gd_side, gd_side, src_side] + distances = [gd_distance, 0, 0] + shift_factors = [2, 2, 1] + + if is_src_shared: + src_idx = pins.index("source") + pins.pop(src_idx) + sides.pop(src_idx) + distances.pop(src_idx) + shift_factors.pop(src_idx) + + if is_gate_shared: + gate_idx = pins.index("gate") + pins.pop(gate_idx) + sides.pop(gate_idx) + distances.pop(gate_idx) + shift_factors.pop(gate_idx) + drain_idx = pins.index("drain") + shift_factors[drain_idx]=1 + + + if is_src_shared: + for n in range(len(pattern[0])-1): + this_portpfx = "col_" + str(n) + "_" + next_portpfx = "col_" + str(n+1) + "_" + this_port = this_portpfx + "source_" + src_side + next_port = next_portpfx + "source_" + src_side + + ref = multiplier_2dim_arr << c_route(pdk, + multiplier_2dim_arr.ports[this_port], + multiplier_2dim_arr.ports[next_port], + viaoffset=(True,False), + extension=0) + multiplier_2dim_arr.add_ports(ref.get_ports_list(), + prefix="_".join(["route", + str(n), + "source"])) + + if is_gate_shared: + for n in range(len(pattern[0])-1): + this_portpfx = "col_" + str(n) + "_" + next_portpfx = "col_" + str(n+1) + "_" + this_port = this_portpfx + "gate_" + gd_side + next_port = next_portpfx + "gate_" + gd_side + + ref = multiplier_2dim_arr << c_route(pdk, + multiplier_2dim_arr.ports[this_port], + multiplier_2dim_arr.ports[next_port], + viaoffset=(True,False), + extension=0) + multiplier_2dim_arr.add_ports(ref.get_ports_list(), + prefix="_".join(["route", + str(n), + "gate"])) + + correction_factor=0 + for n, element in enumerate(element_positions.keys()): + + print(n , element) + if len(element_positions[element])==1: + correction_factor+=1 + continue + + element_shift = (n - correction_factor )*gd_distance + l_positions = element_positions[element] + for i_pos in range(len(l_positions)-1): + this_portpfx = "col_" + str(l_positions[i_pos]) + "_" + element + "_" + next_portpfx = "col_" + str(l_positions[i_pos+1]) + "_" + element + "_" + + for pin, side, distance, shift_factor in list(zip(pins,sides,distances, shift_factors)): + + this_port = this_portpfx + pin + "_" +side + next_port = next_portpfx + pin + "_" +side + + print(this_port) + print(next_port) + + ref = multiplier_2dim_arr << c_route(pdk, + multiplier_2dim_arr.ports[this_port], + multiplier_2dim_arr.ports[next_port], + viaoffset=(True,False), + extension=to_float(distance)+element_shift*shift_factor) + multiplier_2dim_arr.add_ports(ref.get_ports_list(), + prefix="_".join(["route", + element, + str(l_positions[i_pos]), + pin])) + + return multiplier_2dim_arr + #@cell def nmos( pdk, @@ -370,7 +819,10 @@ def nmos( interfinger_rmult: int=1, tie_layers: tuple[str,str] = ("met2","met1"), substrate_tap_layers: tuple[str,str] = ("met2","met1"), - dummy_routes: bool=True + dummy_routes: bool=True, + pattern: Union[list[str], list[int], None] = None, + is_gate_shared: bool = False, + is_src_shared: bool = False, ) -> Component: """Generic NMOS generator pdk: mapped pdk to use @@ -403,8 +855,14 @@ def nmos( sd_rmult = rmult gate_rmult = 1 interfinger_rmult = ((rmult-1) or 1) + + + level = check_pattern_level(pattern) + + __macro = __mult_array_macro if level<=1 else __mult_2dim_array_macro + # create and add multipliers to nfet - multiplier_arr = __mult_array_macro( + multiplier_arr = __macro( pdk, "n+s/d", width, @@ -415,10 +873,14 @@ def nmos( sd_route_topmet=sd_route_topmet, gate_route_topmet=gate_route_topmet, sd_route_left=sd_route_left, + gd_route_bottom=False, sd_rmult=sd_rmult, gate_rmult=gate_rmult, interfinger_rmult=interfinger_rmult, - dummy_routes=dummy_routes + dummy_routes=dummy_routes, + pattern=pattern, + is_gate_shared=is_gate_shared, + is_src_shared=is_src_shared, ) multiplier_arr_ref = multiplier_arr.ref() nfet.add(multiplier_arr_ref) @@ -442,12 +904,24 @@ def nmos( 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<1: + dummy_port_name = "col_0_" + dummy_port_name + nfet<1: + dummy_port_name = "col_" + str(multipliers[0]-1) + "_" + dummy_port_name + nfet< Component: """Generic PMOS generator pdk: mapped pdk to use @@ -548,8 +1025,14 @@ def pmos( sd_rmult = rmult gate_rmult = 1 interfinger_rmult = ((rmult-1) or 1) + + + level = check_pattern_level(pattern) + + __macro = __mult_array_macro if level<=1 else __mult_2dim_array_macro + # create and add multipliers to nfet - multiplier_arr = __mult_array_macro( + multiplier_arr = __macro( pdk, "p+s/d", width, @@ -560,10 +1043,14 @@ def pmos( sd_route_topmet=sd_route_topmet, gate_route_topmet=gate_route_topmet, sd_route_left=sd_route_left, + gd_route_bottom=True, gate_rmult=gate_rmult, interfinger_rmult=interfinger_rmult, sd_rmult=sd_rmult, - dummy_routes=dummy_routes + dummy_routes=dummy_routes, + pattern=pattern, + is_gate_shared=is_gate_shared, + is_src_shared=is_src_shared, ) multiplier_arr_ref = multiplier_arr.ref() pfet.add(multiplier_arr_ref) @@ -588,12 +1075,24 @@ def pmos( 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<1: + dummy_port_name = "col_0_" + dummy_port_name + pfet<1: + dummy_port_name = "col_" + str(multipliers[0]-1) + "_" + dummy_port_name + pfet<\n" + ], + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "from glayout import MappedPDK, sky130 , gf180"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "id": "68ca5a32-61cf-4e5a-976c-5df72d608575",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from glayout.primitives.fet import nmos, pmos, multiplier, __mult_array_macro"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "id": "03a12ded-d9de-4f65-8f47-2c5908627dcd",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "\u001b[32m2025-09-09 06:31:28.342\u001b[0m | \u001b[1mINFO    \u001b[0m | \u001b[36mgdsfactory.pdk\u001b[0m:\u001b[36mactivate\u001b[0m:\u001b[36m337\u001b[0m - \u001b[1m'gf180' PDK is now active\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "pdk=gf180\n",
+    "pdk.activate()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "id": "4b469912-7e6b-4a3a-99fa-10add936f071",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "single_multiplier=multiplier(pdk,\"n+s/d\",fingers=2,dummy_separation_rmult=2)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "id": "9bbbe436-c307-46af-9334-2095bce7fce2",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "\u001b[32m2025-09-08 07:22:58.108\u001b[0m | \u001b[1mINFO    \u001b[0m | \u001b[36mgdsfactory.klive\u001b[0m:\u001b[36mshow\u001b[0m:\u001b[36m55\u001b[0m - \u001b[1mMessage from klive: {\"version\": \"0.3.3\", \"klayout_version\": \"0.30.2\", \"type\": \"reload\", \"file\": \"/tmp/gdsfactory/multiplier_84ed52fc.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "single_multiplier.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "5bdeedb2-d61f-45b3-a9c1-b50859271bae",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "array_multipliers=__mult_array_macro(pdk,\"n+s/d\",fingers=2,multipliers=3,dummy=(True,False))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "e1b3bb7b-37f0-4737-b178-cbcfdde96f78",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/headless/conda-env/miniconda3/envs/GLdev/lib/python3.10/site-packages/gdsfactory/show.py:40: UserWarning: Unnamed cells, 1 in 'Unnamed_6482c14b$1'\n",
+      "  gdspath = component.write_gds(\n",
+      "\u001b[32m2025-09-08 07:23:11.857\u001b[0m | \u001b[1mINFO    \u001b[0m | \u001b[36mgdsfactory.klive\u001b[0m:\u001b[36mshow\u001b[0m:\u001b[36m55\u001b[0m - \u001b[1mMessage from klive: {\"version\": \"0.3.3\", \"klayout_version\": \"0.30.2\", \"type\": \"open\", \"file\": \"/tmp/gdsfactory/Unnamed_6482c14b$1.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "array_multipliers.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "id": "276dfb92-66c0-402e-a4c1-ce69e44dc5af",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "array_multipliers=__mult_array_macro(pdk,\"n+s/d\",fingers=2,multipliers=3,dummy=(True,False), \n",
+    "                                     pattern=[\"A\", \"B\", \"C\"], is_gate_shared=True, is_src_shared=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "id": "ab9a3c46-5115-4640-bf9d-dc14cdf77d47",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/headless/conda-env/miniconda3/envs/GLdev/lib/python3.10/site-packages/gdsfactory/show.py:40: UserWarning: Unnamed cells, 1 in 'Unnamed_3126d5c4$1'\n",
+      "  gdspath = component.write_gds(\n",
+      "\u001b[32m2025-09-09 06:31:56.733\u001b[0m | \u001b[1mINFO    \u001b[0m | \u001b[36mgdsfactory.klive\u001b[0m:\u001b[36mshow\u001b[0m:\u001b[36m55\u001b[0m - \u001b[1mMessage from klive: {\"version\": \"0.3.3\", \"klayout_version\": \"0.30.2\", \"type\": \"open\", \"file\": \"/tmp/gdsfactory/Unnamed_3126d5c4$1.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "array_multipliers.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "c29b2e88-d18e-485c-81eb-aa2484d2be1a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from glayout.primitives.fet import multiplier, __mult_array_macro, __mult_2dim_array_macro"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "adc8cc94-3474-4d35-947b-1357c9cd4c43",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "{'A': [0, 1, 2], 'C': [1], 'B': [0, 2]}\n",
+      "col_0_gate_S\n",
+      "col_0_A_drain_S\n",
+      "1.1\n",
+      "0 A\n",
+      "col_0_A_drain_S\n",
+      "col_1_A_drain_S\n",
+      "col_1_A_drain_S\n",
+      "col_2_A_drain_S\n",
+      "1 C\n",
+      "2 B\n",
+      "col_0_B_drain_S\n",
+      "col_2_B_drain_S\n"
+     ]
+    }
+   ],
+   "source": [
+    "array_multipliers_2dim=__mult_2dim_array_macro(pdk,\"n+s/d\",fingers=2,multipliers=(3,3), \n",
+    "                                     pattern=[[\"A\",\"C\",\"B\"],[\"B\",\"A\",\"A\"],[\"A\",\"C\",\"B\"]],is_src_shared=True,\n",
+    "                                              is_gate_shared=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "id": "00a31e75-8d66-4a7b-97f9-5517a47143ce",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/headless/conda-env/miniconda3/envs/GLdev/lib/python3.10/site-packages/gdsfactory/show.py:40: UserWarning: Unnamed cells, 3 in 'temp_multiplier_array$2'\n",
+      "  gdspath = component.write_gds(\n",
+      "\u001b[32m2025-09-09 06:32:24.757\u001b[0m | \u001b[1mINFO    \u001b[0m | \u001b[36mgdsfactory.klive\u001b[0m:\u001b[36mshow\u001b[0m:\u001b[36m55\u001b[0m - \u001b[1mMessage from klive: {\"version\": \"0.3.3\", \"klayout_version\": \"0.30.2\", \"type\": \"open\", \"file\": \"/tmp/gdsfactory/temp_multiplier_array$2.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "array_multipliers_2dim.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "id": "28608293-b5ad-4598-abb1-4dc07749672a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from glayout.primitives.fet import nmos, pmos"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "id": "3706c1ef-3dc7-4884-9cf1-6c7780f1f4f8",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "{'A': [0, 1, 2], 'C': [1], 'B': [0, 2]}\n",
+      "col_0_gate_S\n",
+      "col_0_A_drain_S\n",
+      "1.1\n",
+      "0 A\n",
+      "col_0_A_drain_N\n",
+      "col_1_A_drain_N\n",
+      "col_1_A_drain_N\n",
+      "col_2_A_drain_N\n",
+      "1 C\n",
+      "2 B\n",
+      "col_0_B_drain_N\n",
+      "col_2_B_drain_N\n"
+     ]
+    }
+   ],
+   "source": [
+    "nmos_c= nmos(pdk,fingers=2,multipliers=(3,3),with_dummy=True,\n",
+    "             pattern=[[\"A\",\"C\",\"B\"],[\"B\",\"A\",\"A\"],[\"A\",\"C\",\"B\"]],is_src_shared=True,\n",
+    "                                              is_gate_shared=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "id": "0ca4d6ba-c208-47bb-a65f-0546184e52de",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/headless/conda-env/miniconda3/envs/GLdev/lib/python3.10/site-packages/gdsfactory/show.py:40: UserWarning: Unnamed cells, 1 in 'Unnamed_9668c76a'\n",
+      "  gdspath = component.write_gds(\n",
+      "\u001b[32m2025-09-09 06:32:53.987\u001b[0m | \u001b[1mINFO    \u001b[0m | \u001b[36mgdsfactory.klive\u001b[0m:\u001b[36mshow\u001b[0m:\u001b[36m55\u001b[0m - \u001b[1mMessage from klive: {\"version\": \"0.3.3\", \"klayout_version\": \"0.30.2\", \"type\": \"open\", \"file\": \"/tmp/gdsfactory/Unnamed_9668c76a.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "nmos_c.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "id": "f63d0845-5f4b-4194-9786-842514564dce",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "{'A': [0, 1, 2], 'C': [1], 'B': [0, 2]}\n",
+      "col_0_gate_S\n",
+      "col_0_A_drain_S\n",
+      "1.1\n",
+      "0 A\n",
+      "col_0_A_drain_S\n",
+      "col_1_A_drain_S\n",
+      "col_1_A_drain_S\n",
+      "col_2_A_drain_S\n",
+      "1 C\n",
+      "2 B\n",
+      "col_0_B_drain_S\n",
+      "col_2_B_drain_S\n"
+     ]
+    }
+   ],
+   "source": [
+    "pmos_c= pmos(pdk,fingers=2,multipliers=(3,3),with_dummy=True,\n",
+    "             pattern=[[\"A\",\"C\",\"B\"],[\"B\",\"A\",\"A\"],[\"A\",\"C\",\"B\"]],is_src_shared=True,\n",
+    "                                              is_gate_shared=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "id": "b596abfc-4f94-4d06-b9f3-161511bcc4df",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/headless/conda-env/miniconda3/envs/GLdev/lib/python3.10/site-packages/gdsfactory/show.py:40: UserWarning: Unnamed cells, 1 in 'Unnamed_b364fed1'\n",
+      "  gdspath = component.write_gds(\n",
+      "\u001b[32m2025-09-09 06:33:10.622\u001b[0m | \u001b[1mINFO    \u001b[0m | \u001b[36mgdsfactory.klive\u001b[0m:\u001b[36mshow\u001b[0m:\u001b[36m55\u001b[0m - \u001b[1mMessage from klive: {\"version\": \"0.3.3\", \"klayout_version\": \"0.30.2\", \"type\": \"open\", \"file\": \"/tmp/gdsfactory/Unnamed_b364fed1.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "pmos_c.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "id": "e26c80cb-6857-40d0-a4a1-5d2ef19a4f6f",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "PosixPath('/foss/pdks/gf180mcuD/libs.tech/magic/gf180mcuD.magicrc')"
+      ]
+     },
+     "execution_count": 9,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from pathlib import Path\n",
+    "import os\n",
+    "import subprocess\n",
+    "import tempfile\n",
+    "magicrc_file = Path(os.environ['PDKPATH']) / \"libs.tech\" / \"magic\" / f\"{os.environ['PDK']}.magicrc\"\n",
+    "magicrc_file"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "id": "f9a0478d-23ba-4bc6-bb4c-f8a7f2452820",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def extract_pex(design, path_to_dir):\n",
+    "    design_name=design.name\n",
+    "    if not path_to_dir.exists():\n",
+    "        path_to_dir.mkdir(parents=True, exist_ok=False)\n",
+    "    \n",
+    "    pex_path = path_to_dir / f\"{design_name}_pex.spice\"\n",
+    "    gds_path = path_to_dir / f\"{design_name}.gds\"\n",
+    "    \n",
+    "    design.write_gds(str(gds_path))\n",
+    "        \n",
+    "    magic_script_content = f\"\"\"\n",
+    "    drc off            \n",
+    "    gds flatglob *\\\\$\\\\$*\n",
+    "    gds read {gds_path}\n",
+    "    \n",
+    "    flatten {design_name}\n",
+    "    load {design_name}\n",
+    "    select top cell\n",
+    "    extract do local\n",
+    "    extract all\n",
+    "    ext2sim labels on\n",
+    "    ext2sim\n",
+    "    extresist tolerance 10\n",
+    "    extresist\n",
+    "    ext2spice lvs\n",
+    "    ext2spice cthresh 0\n",
+    "    ext2spice extresist on\n",
+    "    ext2spice -o {str(pex_path)}\n",
+    "    exit\n",
+    "    \"\"\"\n",
+    "    \n",
+    "    with tempfile.NamedTemporaryFile(mode='w', delete=False) as magic_script_file:\n",
+    "        magic_script_file.write(magic_script_content)\n",
+    "        magic_script_path = magic_script_file.name\n",
+    "        \n",
+    "    magic_cmd = f\"bash -c 'magic -rcfile {magicrc_file} -noconsole -dnull < {magic_script_path}'\",\n",
+    "    magic_subproc = subprocess.run(\n",
+    "        magic_cmd, \n",
+    "        shell=True,\n",
+    "        check=True,\n",
+    "        capture_output=True\n",
+    "    )\n",
+    "    \n",
+    "    magic_subproc_code = magic_subproc.returncode\n",
+    "    magic_subproc_out = magic_subproc.stdout.decode('utf-8')\n",
+    "    print(magic_subproc_out)\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "id": "8af5d9f3-4bcd-46f8-b02b-20f0ed932cbb",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/tmp/ipykernel_465/2536090899.py:9: UserWarning: Unnamed cells, 1 in 'Unnamed_848148dc'\n",
+      "  design.write_gds(str(gds_path))\n",
+      "\u001b[32m2025-09-02 06:57:41.946\u001b[0m | \u001b[1mINFO    \u001b[0m | \u001b[36mgdsfactory.component\u001b[0m:\u001b[36m_write_library\u001b[0m:\u001b[36m1851\u001b[0m - \u001b[1mWrote to '/foss/designs/gLayout/tutorial/ext/Unnamed_848148dc/Unnamed_848148dc.gds'\u001b[0m\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\n",
+      "Magic 8.3 revision 528 - Compiled on Wed Jun 18 09:45:25 PM CEST 2025.\n",
+      "Starting magic under Tcl interpreter\n",
+      "Using the terminal as the console.\n",
+      "WARNING: RLIMIT_NOFILE is above 1024 and Tcl_Version<9 this may cause runtime issues [rlim_cur=4096]\n",
+      "Using NULL graphics device.\n",
+      "Processing system .magicrc file\n",
+      "Sourcing design .magicrc for technology gf180mcuD ...\n",
+      "10 Magic internal units = 1 Lambda\n",
+      "Input style import: scaleFactor=10, multiplier=2\n",
+      "The following types are not handled by extraction and will be treated as non-electrical types:\n",
+      "    obsactive mvobsactive filldiff fillpoly m1hole obsm1 fillm1 obsv1 m2hole obsm2 fillm2 obsv2 m3hole obsm3 fillm3 m4hole obsm4 fillm4 m5hole obsm5 fillm5 glass fillblock lvstext obscomment \n",
+      "Scaled tech values by 10 / 1 to match internal grid scaling\n",
+      "Loading gf180mcuD Device Generator Menu ...\n",
+      "Using technology \"gf180mcuD\", version 1.0.525-0-gf2e289d\n",
+      "Warning: Calma reading is not undoable!  I hope that's OK.\n",
+      "Library written using GDS-II Release 6.0\n",
+      "Library name: library\n",
+      "Reading \"Unnamed_848148dc\".\n",
+      "Extracting Unnamed_848148dc into Unnamed_848148dc.ext:\n",
+      "Unnamed_848148dc: 3 warnings\n",
+      "exttosim finished.\n",
+      "Adding  a_162_n1619#; Tnew = 0.07ns, Told = 0.00ns\n",
+      "Adding  a_n44_n1619#; Tnew = 0.07ns, Told = 0.00ns\n",
+      "Total Nets: 8\n",
+      "Nets extracted: 3 (0.375000)\n",
+      "Nets output: 2 (0.250000)\n",
+      "exttospice finished.\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "path_to_dir = Path(\"/foss/designs/gLayout/tutorial\").resolve() / \"ext\" / nmos_c.name\n",
+    "extract_pex(nmos_c, path_to_dir)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3927146c-31be-4740-892f-66fca3c520ef",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "GLdev",
+   "language": "python",
+   "name": "gldev"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.18"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}