From 85cb06885fcf3f1f22154ed51da0d5c239f0f9d3 Mon Sep 17 00:00:00 2001 From: Luighi Date: Fri, 5 Sep 2025 03:42:29 -0300 Subject: [PATCH 1/5] Add custom patterning to fet --- src/glayout/primitives/fet.py | 500 ++++++++++++++++++++++++++++++---- 1 file changed, 449 insertions(+), 51 deletions(-) diff --git a/src/glayout/primitives/fet.py b/src/glayout/primitives/fet.py index 541d36dd..92810cd5 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,27 @@ 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, 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, + 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 +313,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,31 +332,143 @@ 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: + sd_pattern_distances = [distance*2*n for n in range(len(unique_elements))] + gate_pattern_distances = [distance*n for n in range(len(unique_elements))] + sd_distances_by_element = dict(zip(unique_elements,sd_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" + + dimension_wo_routing = evaluate_bbox(multiplier_arr) + 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) + if pattern is None: + 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) + + else: + 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] - + sd_distances_by_element[unique_elements[rownum]] + - to_float(src_extension), + (multiplier_arr.ymax + + multiplier_arr.ymin)/2)) + + nref_port_source= move(ref_port_source, + destination=(ref_port_source.center[0] - + sd_distances_by_element[unique_elements[rownum]] + - to_float(drain_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) + + source_route_ref = align_comp_to_port(sample_route.copy(), + nref_port_source, + alignment=("l",'c')) + multiplier_arr.add(source_route_ref) + multiplier_arr.add_ports(source_route_ref.get_ports_list(), + prefix=this_source_pfx) + + gate_route_ref = align_comp_to_port(sample_route.copy(), + nref_port_gate, + alignment=("r",'c')) + multiplier_arr.add(gate_route_ref) + 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] + source_route=multiplier_arr.ports[str(pat)+"_source_"+gate_side] + gate_route=multiplier_arr.ports[str(pat)+"_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"]: @@ -344,12 +479,233 @@ def __mult_array_macro( # 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, +) -> 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, + 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], + 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], + 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 + + sample_element = list(element_positions.keys())[0] + sample_col=element_positions[sample_element][0] + sample_gate_port="col_"+ str(sample_col) +"_" + sample_element + "_gate_S" + sample_drain_port=( "col_"+ str(sample_col)+"_" + sample_element + + "_drain_S" ) + base_port_width= multiplier_2dim_arr[sample_gate_port].width + collector_port_width = multiplier_2dim_arr[sample_drain_port].width + gd_distance = base_port_width/2 + collector_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" + gate_side = "N" if gd_route_bottom else "S" + + pins = ["drain", "gate", "source"] + sides = [gd_side, gd_side, gate_side] + distances = [0, gd_distance, 0] + shift_factors = [2, 2, 1] + + 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 +726,8 @@ 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, ) -> Component: """Generic NMOS generator pdk: mapped pdk to use @@ -403,8 +760,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 +778,12 @@ 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 ) multiplier_arr_ref = multiplier_arr.ref() nfet.add(multiplier_arr_ref) @@ -442,12 +807,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 +926,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 +944,12 @@ 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 ) multiplier_arr_ref = multiplier_arr.ref() pfet.add(multiplier_arr_ref) @@ -588,12 +974,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< Date: Fri, 5 Sep 2025 03:46:34 -0300 Subject: [PATCH 2/5] Add notebook to test custom patterns with fet --- tutorial/test_fet.ipynb | 531 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 531 insertions(+) create mode 100644 tutorial/test_fet.ipynb diff --git a/tutorial/test_fet.ipynb b/tutorial/test_fet.ipynb new file mode 100644 index 00000000..d8bea7ed --- /dev/null +++ b/tutorial/test_fet.ipynb @@ -0,0 +1,531 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "fdd448d4-75b7-4d1c-80a0-098ca335956f", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a0d25196-a3b8-444d-a259-d1a5d743fc0e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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": 22,
+   "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": 7,
+   "id": "03a12ded-d9de-4f65-8f47-2c5908627dcd",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pdk=gf180\n",
+    "pdk.activate()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "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": 9,
+   "id": "9bbbe436-c307-46af-9334-2095bce7fce2",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "\u001b[32m2025-09-05 06:09:39.798\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/multiplier_84ed52fc.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "single_multiplier.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "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": 13,
+   "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_7f4cd017$1'\n",
+      "  gdspath = component.write_gds(\n",
+      "\u001b[32m2025-09-05 06:34:51.293\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_7f4cd017$1.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "array_multipliers.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "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\"])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "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_d37b1343$1'\n",
+      "  gdspath = component.write_gds(\n",
+      "\u001b[32m2025-09-05 06:44:38.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_d37b1343$1.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "array_multipliers.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 34,
+   "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": 35,
+   "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_A_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_0_A_gate_S\n",
+      "col_1_A_gate_S\n",
+      "col_0_A_source_N\n",
+      "col_1_A_source_N\n",
+      "col_1_A_drain_S\n",
+      "col_2_A_drain_S\n",
+      "col_1_A_gate_S\n",
+      "col_2_A_gate_S\n",
+      "col_1_A_source_N\n",
+      "col_2_A_source_N\n",
+      "1 C\n",
+      "2 B\n",
+      "col_0_B_drain_S\n",
+      "col_2_B_drain_S\n",
+      "col_0_B_gate_S\n",
+      "col_2_B_gate_S\n",
+      "col_0_B_source_N\n",
+      "col_2_B_source_N\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\"]])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 36,
+   "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$13'\n",
+      "  gdspath = component.write_gds(\n",
+      "\u001b[32m2025-09-05 07:38:41.459\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$13.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "array_multipliers_2dim.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 55,
+   "id": "28608293-b5ad-4598-abb1-4dc07749672a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from glayout.primitives.fet import nmos, pmos"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 53,
+   "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_A_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_0_A_gate_N\n",
+      "col_1_A_gate_N\n",
+      "col_0_A_source_S\n",
+      "col_1_A_source_S\n",
+      "col_1_A_drain_N\n",
+      "col_2_A_drain_N\n",
+      "col_1_A_gate_N\n",
+      "col_2_A_gate_N\n",
+      "col_1_A_source_S\n",
+      "col_2_A_source_S\n",
+      "1 C\n",
+      "2 B\n",
+      "col_0_B_drain_N\n",
+      "col_2_B_drain_N\n",
+      "col_0_B_gate_N\n",
+      "col_2_B_gate_N\n",
+      "col_0_B_source_S\n",
+      "col_2_B_source_S\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\"]])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 54,
+   "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_435d7764'\n",
+      "  gdspath = component.write_gds(\n",
+      "\u001b[32m2025-09-05 08:28:11.969\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_435d7764.gds\"}\u001b[0m\n"
+     ]
+    }
+   ],
+   "source": [
+    "nmos_c.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 58,
+   "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_A_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_0_A_gate_S\n",
+      "col_1_A_gate_S\n",
+      "col_0_A_source_N\n",
+      "col_1_A_source_N\n",
+      "col_1_A_drain_S\n",
+      "col_2_A_drain_S\n",
+      "col_1_A_gate_S\n",
+      "col_2_A_gate_S\n",
+      "col_1_A_source_N\n",
+      "col_2_A_source_N\n",
+      "1 C\n",
+      "2 B\n",
+      "col_0_B_drain_S\n",
+      "col_2_B_drain_S\n",
+      "col_0_B_gate_S\n",
+      "col_2_B_gate_S\n",
+      "col_0_B_source_N\n",
+      "col_2_B_source_N\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\"]])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 59,
+   "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_8941483a'\n",
+      "  gdspath = component.write_gds(\n",
+      "\u001b[32m2025-09-05 08:37:00.996\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_8941483a.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
+}

From 49d939032592215eed6397787de787181d700d34 Mon Sep 17 00:00:00 2001
From: Luighi 
Date: Sun, 7 Sep 2025 23:41:56 -0300
Subject: [PATCH 3/5] Fix generation when there is just 1 row

---
 src/glayout/primitives/fet.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/glayout/primitives/fet.py b/src/glayout/primitives/fet.py
index 92810cd5..ad8c87a2 100644
--- a/src/glayout/primitives/fet.py
+++ b/src/glayout/primitives/fet.py
@@ -353,8 +353,8 @@ def __mult_array_macro(
 
     dimension_wo_routing = evaluate_bbox(multiplier_arr)
 
-    if routing and multipliers > 1:
-        if pattern is None:
+    if routing:
+        if pattern is None  and multipliers > 1:
             for rownum in range(multipliers-1):
                 thismult = "multiplier_" + str(rownum) + "_"
                 nextmult = "multiplier_" + str(rownum+1) + "_"
@@ -377,7 +377,7 @@ def __mult_array_macro(
                 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)
 
-        else:
+        elif pattern is not None:
             for rownum in range(len(unique_elements)):
                 this_id_pfx = unique_elements[rownum] + "_"
 

From 788e7c71286f7115db734ef93806f4d8944cbbf4 Mon Sep 17 00:00:00 2001
From: Luighi 
Date: Tue, 9 Sep 2025 01:34:22 -0300
Subject: [PATCH 4/5] Add gate_shared and src_shared options for the routing

---
 src/glayout/primitives/fet.py | 173 +++++++++++++++++++++++++++-------
 1 file changed, 137 insertions(+), 36 deletions(-)

diff --git a/src/glayout/primitives/fet.py b/src/glayout/primitives/fet.py
index ad8c87a2..5dc8e04d 100644
--- a/src/glayout/primitives/fet.py
+++ b/src/glayout/primitives/fet.py
@@ -275,12 +275,14 @@ 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,
+    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,
     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:
@@ -343,9 +345,20 @@ def __mult_array_macro(
     drain_extension = to_decimal(to_float(src_extension)+ distance )
 
     if pattern is not None:
-        sd_pattern_distances = [distance*2*n for n in range(len(unique_elements))]
+        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))]
-        sd_distances_by_element = dict(zip(unique_elements,sd_pattern_distances))
+
+        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"
@@ -395,15 +408,15 @@ def __mult_array_macro(
 
                 nref_port_drain= move(ref_port_drain,
                                      destination=(ref_port_drain.center[0] -
-                                                  sd_distances_by_element[unique_elements[rownum]]
-                                                  - to_float(src_extension),
+                                                  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] -
-                                                  sd_distances_by_element[unique_elements[rownum]]
-                                                  - to_float(drain_extension),
+                                                  src_distances_by_element[unique_elements[rownum]]
+                                                  - to_float(src_extension),
                                                   (multiplier_arr.ymax +
                                                       multiplier_arr.ymin)/2))
 
@@ -426,19 +439,25 @@ def __mult_array_macro(
                 multiplier_arr.add_ports(drain_route_ref.get_ports_list(),
                                          prefix=this_drain_pfx)
 
-                source_route_ref = align_comp_to_port(sample_route.copy(),
-                                       nref_port_source,
-                                       alignment=("l",'c'))
-                multiplier_arr.add(source_route_ref)
-                multiplier_arr.add_ports(source_route_ref.get_ports_list(),
-                                             prefix=this_source_pfx)
-
-                gate_route_ref = align_comp_to_port(sample_route.copy(),
-                                       nref_port_gate,
-                                       alignment=("r",'c'))
-                multiplier_arr.add(gate_route_ref)
-                multiplier_arr.add_ports(gate_route_ref.get_ports_list(),
-                                         prefix=this_gate_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)
 
@@ -456,8 +475,17 @@ def __mult_array_macro(
                 this_gate = multiplier_arr.ports[gatepfx+gate_side]
 
                 drain_route=multiplier_arr.ports[str(pat)+"_drain_"+gate_side]
-                source_route=multiplier_arr.ports[str(pat)+"_source_"+gate_side]
-                gate_route=multiplier_arr.ports[str(pat)+"_gate_"+sd_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
@@ -471,11 +499,12 @@ def __mult_array_macro(
                                                                  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
@@ -505,6 +534,8 @@ def __mult_2dim_array_macro(
     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
@@ -545,6 +576,8 @@ def __mult_2dim_array_macro(
             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)
@@ -597,6 +630,8 @@ def __mult_2dim_array_macro(
                     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
             )
@@ -619,6 +654,8 @@ def __mult_2dim_array_macro(
                     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
             )
@@ -650,27 +687,83 @@ def __mult_2dim_array_macro(
     # 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]
-    sample_gate_port="col_"+ str(sample_col) +"_" + sample_element + "_gate_S"
+    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" )
-    base_port_width= multiplier_2dim_arr[sample_gate_port].width
-    collector_port_width = multiplier_2dim_arr[sample_drain_port].width
-    gd_distance = base_port_width/2 + collector_port_width/2 + 2*pdk.get_grule("met4")["min_separation"]
+    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"
-    gate_side = "N" if gd_route_bottom else "S"
+    src_side = "N" if gd_route_bottom else "S"
 
     pins = ["drain", "gate", "source"]
-    sides = [gd_side, gd_side, gate_side]
-    distances = [0, gd_distance, 0]
+    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()):
 
@@ -728,6 +821,8 @@ def nmos(
     substrate_tap_layers: tuple[str,str] = ("met2","met1"),
     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
@@ -783,7 +878,9 @@ def nmos(
         gate_rmult=gate_rmult,
         interfinger_rmult=interfinger_rmult,
         dummy_routes=dummy_routes,
-        pattern=pattern
+        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)
@@ -894,6 +991,8 @@ def pmos(
     substrate_tap_layers: tuple[str,str] = ("met2","met1"),
     dummy_routes: bool=True,
     pattern: Union[list[str], list[int], None] = None,
+    is_gate_shared: bool = False,
+    is_src_shared: bool = False,
 ) -> Component:
     """Generic PMOS generator
     pdk: mapped pdk to use
@@ -949,7 +1048,9 @@ def pmos(
         interfinger_rmult=interfinger_rmult,
         sd_rmult=sd_rmult,
         dummy_routes=dummy_routes,
-        pattern=pattern
+        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)

From 284876a466410412091e542e7adaf64138d19154 Mon Sep 17 00:00:00 2001
From: Luighi 
Date: Tue, 9 Sep 2025 01:34:58 -0300
Subject: [PATCH 5/5] Add tests for shared gate and source

---
 tutorial/test_fet.ipynb | 123 ++++++++++++++++------------------------
 1 file changed, 49 insertions(+), 74 deletions(-)

diff --git a/tutorial/test_fet.ipynb b/tutorial/test_fet.ipynb
index d8bea7ed..706c165c 100644
--- a/tutorial/test_fet.ipynb
+++ b/tutorial/test_fet.ipynb
@@ -44,7 +44,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 3,
    "id": "68ca5a32-61cf-4e5a-976c-5df72d608575",
    "metadata": {},
    "outputs": [],
@@ -54,10 +54,18 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 4,
    "id": "03a12ded-d9de-4f65-8f47-2c5908627dcd",
    "metadata": {},
-   "outputs": [],
+   "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()"
@@ -65,7 +73,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 5,
    "id": "4b469912-7e6b-4a3a-99fa-10add936f071",
    "metadata": {},
    "outputs": [],
@@ -75,7 +83,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 6,
    "id": "9bbbe436-c307-46af-9334-2095bce7fce2",
    "metadata": {},
    "outputs": [
@@ -83,7 +91,7 @@
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "\u001b[32m2025-09-05 06:09:39.798\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/multiplier_84ed52fc.gds\"}\u001b[0m\n"
+      "\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"
      ]
     }
    ],
@@ -93,7 +101,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 7,
    "id": "5bdeedb2-d61f-45b3-a9c1-b50859271bae",
    "metadata": {},
    "outputs": [],
@@ -103,7 +111,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 8,
    "id": "e1b3bb7b-37f0-4737-b178-cbcfdde96f78",
    "metadata": {},
    "outputs": [
@@ -111,9 +119,9 @@
      "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_7f4cd017$1'\n",
+      "/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-05 06:34:51.293\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_7f4cd017$1.gds\"}\u001b[0m\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"
      ]
     }
    ],
@@ -123,18 +131,18 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "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\"])"
+    "                                     pattern=[\"A\", \"B\", \"C\"], is_gate_shared=True, is_src_shared=True)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": 6,
    "id": "ab9a3c46-5115-4640-bf9d-dc14cdf77d47",
    "metadata": {},
    "outputs": [
@@ -142,9 +150,9 @@
      "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_d37b1343$1'\n",
+      "/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-05 06:44:38.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_d37b1343$1.gds\"}\u001b[0m\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"
      ]
     }
    ],
@@ -154,7 +162,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 34,
+   "execution_count": 7,
    "id": "c29b2e88-d18e-485c-81eb-aa2484d2be1a",
    "metadata": {},
    "outputs": [],
@@ -164,7 +172,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 35,
+   "execution_count": 8,
    "id": "adc8cc94-3474-4d35-947b-1357c9cd4c43",
    "metadata": {},
    "outputs": [
@@ -173,41 +181,30 @@
      "output_type": "stream",
      "text": [
       "{'A': [0, 1, 2], 'C': [1], 'B': [0, 2]}\n",
-      "col_0_A_gate_S\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_0_A_gate_S\n",
-      "col_1_A_gate_S\n",
-      "col_0_A_source_N\n",
-      "col_1_A_source_N\n",
       "col_1_A_drain_S\n",
       "col_2_A_drain_S\n",
-      "col_1_A_gate_S\n",
-      "col_2_A_gate_S\n",
-      "col_1_A_source_N\n",
-      "col_2_A_source_N\n",
       "1 C\n",
       "2 B\n",
       "col_0_B_drain_S\n",
-      "col_2_B_drain_S\n",
-      "col_0_B_gate_S\n",
-      "col_2_B_gate_S\n",
-      "col_0_B_source_N\n",
-      "col_2_B_source_N\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\"]])"
+    "                                     pattern=[[\"A\",\"C\",\"B\"],[\"B\",\"A\",\"A\"],[\"A\",\"C\",\"B\"]],is_src_shared=True,\n",
+    "                                              is_gate_shared=True)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 36,
+   "execution_count": 9,
    "id": "00a31e75-8d66-4a7b-97f9-5517a47143ce",
    "metadata": {},
    "outputs": [
@@ -215,9 +212,9 @@
      "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$13'\n",
+      "/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-05 07:38:41.459\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$13.gds\"}\u001b[0m\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"
      ]
     }
    ],
@@ -227,7 +224,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 55,
+   "execution_count": 10,
    "id": "28608293-b5ad-4598-abb1-4dc07749672a",
    "metadata": {},
    "outputs": [],
@@ -237,7 +234,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 53,
+   "execution_count": 11,
    "id": "3706c1ef-3dc7-4884-9cf1-6c7780f1f4f8",
    "metadata": {},
    "outputs": [
@@ -246,41 +243,30 @@
      "output_type": "stream",
      "text": [
       "{'A': [0, 1, 2], 'C': [1], 'B': [0, 2]}\n",
-      "col_0_A_gate_S\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_0_A_gate_N\n",
-      "col_1_A_gate_N\n",
-      "col_0_A_source_S\n",
-      "col_1_A_source_S\n",
       "col_1_A_drain_N\n",
       "col_2_A_drain_N\n",
-      "col_1_A_gate_N\n",
-      "col_2_A_gate_N\n",
-      "col_1_A_source_S\n",
-      "col_2_A_source_S\n",
       "1 C\n",
       "2 B\n",
       "col_0_B_drain_N\n",
-      "col_2_B_drain_N\n",
-      "col_0_B_gate_N\n",
-      "col_2_B_gate_N\n",
-      "col_0_B_source_S\n",
-      "col_2_B_source_S\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\"]])"
+    "             pattern=[[\"A\",\"C\",\"B\"],[\"B\",\"A\",\"A\"],[\"A\",\"C\",\"B\"]],is_src_shared=True,\n",
+    "                                              is_gate_shared=True)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 54,
+   "execution_count": 12,
    "id": "0ca4d6ba-c208-47bb-a65f-0546184e52de",
    "metadata": {},
    "outputs": [
@@ -288,9 +274,9 @@
      "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_435d7764'\n",
+      "/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-05 08:28:11.969\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_435d7764.gds\"}\u001b[0m\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"
      ]
     }
    ],
@@ -300,7 +286,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 58,
+   "execution_count": 13,
    "id": "f63d0845-5f4b-4194-9786-842514564dce",
    "metadata": {},
    "outputs": [
@@ -309,41 +295,30 @@
      "output_type": "stream",
      "text": [
       "{'A': [0, 1, 2], 'C': [1], 'B': [0, 2]}\n",
-      "col_0_A_gate_S\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_0_A_gate_S\n",
-      "col_1_A_gate_S\n",
-      "col_0_A_source_N\n",
-      "col_1_A_source_N\n",
       "col_1_A_drain_S\n",
       "col_2_A_drain_S\n",
-      "col_1_A_gate_S\n",
-      "col_2_A_gate_S\n",
-      "col_1_A_source_N\n",
-      "col_2_A_source_N\n",
       "1 C\n",
       "2 B\n",
       "col_0_B_drain_S\n",
-      "col_2_B_drain_S\n",
-      "col_0_B_gate_S\n",
-      "col_2_B_gate_S\n",
-      "col_0_B_source_N\n",
-      "col_2_B_source_N\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\"]])"
+    "             pattern=[[\"A\",\"C\",\"B\"],[\"B\",\"A\",\"A\"],[\"A\",\"C\",\"B\"]],is_src_shared=True,\n",
+    "                                              is_gate_shared=True)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 59,
+   "execution_count": 14,
    "id": "b596abfc-4f94-4d06-b9f3-161511bcc4df",
    "metadata": {},
    "outputs": [
@@ -351,9 +326,9 @@
      "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_8941483a'\n",
+      "/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-05 08:37:00.996\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_8941483a.gds\"}\u001b[0m\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"
      ]
     }
    ],