diff --git a/src/glayout/cells/composite/chopper_switch/README.md b/src/glayout/cells/composite/chopper_switch/README.md
new file mode 100644
index 00000000..d4f2ef14
--- /dev/null
+++ b/src/glayout/cells/composite/chopper_switch/README.md
@@ -0,0 +1,74 @@
+# Chopper Switch Cell
+
+GenYZ Team: Oct 17 2025
+
+## Schematic
+
+
+This circuit is a differential chopper switch. It alternates the signal polarity between the differential inputs (IN+, IN-) and outputs (OUT+, OUT-). This implementation is constructed using pre-designed tgswitch components as the core switching elements. The opposing control signals, CLK and CLKB, determine which tgswitch path is active, selecting either a straight-through or a crossover connection.
+
+## Parametrizing the Chopper Switch Block
+**Parameters:**
+- **pdk:** Which PDK to use.
+- **width:** Width per finger (µm), in a tuple for PFET and NFET respectively.
+- **length:** Length per finger (µm), in a tuple for PFET and NFET respectively.
+- **fingers:** Number of fingers per transistor, in a tuple for PFET and NFET respectively.
+- **multipliers:** Parallel device multiplier (m-factor), in a tuple for PFET and NFET respectively.
+- **dummy_1:** Enable PFET dummy gates, in a tuple for left and right dummy respectively.
+- **dummy_2:** Enable NFET dummy gates, in a tuple for left and right dummy respectively.
+- **tie_layers1:** PFET body-tie routing layers, in a tuple (X metal, Y metal).
+- **tie_layers2:** NFET body-tie routing layers, in a tuple (X metal, Y metal).
+- **sd_rmult:** Integer multiplier for source/drain contact routing width (reduces on-resistance).
+- **kwargs:** Additional parameters passed directly to pdk.nmos() and pdk.pmos().
+```
+def chopper_switch(
+ pdk: MappedPDK,
+ width: tuple[float,float] = (10,10),
+ length: tuple[float,float] = (0.5,0.5),
+ fingers: tuple[int,int] = (6,6),
+ multipliers: tuple[int,int] = (1,1),
+ dummy_1: tuple[bool,bool] = (True,True),
+ dummy_2: tuple[bool,bool] = (True,True),
+ tie_layers1: tuple[str,str] = ("met2","met1"),
+ tie_layers2: tuple[str,str] = ("met2","met1"),
+ sd_rmult: int=1,
+ **kwargs
+ ) -> Component:
+```
+
+###
+
+### GDS generated
+
+
+### DRC Report
+```
+using default pdk_root
+Defaulting to stale magic_commands.tcl
+
+Magic 8.3 revision 528 - Compiled on Wed Jun 18 09:45:25 PM CEST 2025.
+Starting magic under Tcl interpreter
+Using the terminal as the console.
+WARNING: RLIMIT_NOFILE is above 1024 and Tcl_Version<9 this may cause runtime issues [rlim_cur=1048576]
+Using NULL graphics device.
+Processing system .magicrc file
+Sourcing design .magicrc for technology gf180mcuD ...
+10 Magic internal units = 1 Lambda
+Input style import: scaleFactor=10, multiplier=2
+The following types are not handled by extraction and will be treated as non-electrical types:
+ 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
+Scaled tech values by 10 / 1 to match internal grid scaling
+Loading gf180mcuD Device Generator Menu ...
+Loading "/tmp/tmpsvvb6hl4/magic_commands.tcl" from command line.
+Warning: Calma reading is not undoable! I hope that's OK.
+Library written using GDS-II Release 6.0
+Library name: library
+Reading "cswitch$3".
+[INFO]: Loading cswitch$3
+
+Loading DRC CIF style.
+No errors found.
+[INFO]: DONE with /tmp/tmpsvvb6hl4/cswitch$3.rpt
+
+Using technology "gf180mcuD", version 1.0.525-0-gf2e289d
+```
diff --git a/src/glayout/cells/composite/chopper_switch/__init__.py b/src/glayout/cells/composite/chopper_switch/__init__.py
new file mode 100644
index 00000000..85a5a679
--- /dev/null
+++ b/src/glayout/cells/composite/chopper_switch/__init__.py
@@ -0,0 +1,10 @@
+
+###Glayout CSWITCH Cell.
+
+
+from .my_CSWITCH import cswitch,add_cswitch_labels
+
+__all__ = [
+ 'cswitch',
+ 'add_cswitch_labels',
+]
diff --git a/src/glayout/cells/composite/chopper_switch/chopper_switch.py b/src/glayout/cells/composite/chopper_switch/chopper_switch.py
new file mode 100644
index 00000000..680a471d
--- /dev/null
+++ b/src/glayout/cells/composite/chopper_switch/chopper_switch.py
@@ -0,0 +1,314 @@
+from glayout import MappedPDK, sky130 , gf180
+#from gdsfactory.cell import cell
+from gdsfactory import Component
+from gdsfactory.components import text_freetype, rectangle
+from glayout import nmos, pmos
+from glayout import via_stack, via_array
+from glayout import rename_ports_by_orientation
+from glayout import tapring
+from glayout.util.comp_utils import evaluate_bbox, prec_center, prec_ref_center, align_comp_to_port
+from glayout.util.port_utils import add_ports_perimeter,print_ports
+from glayout.util.snap_to_grid import component_snap_to_grid
+from glayout.spice.netlist import Netlist
+from glayout.routing.straight_route import straight_route
+from glayout.routing.c_route import c_route
+from glayout.routing.L_route import L_route
+
+import os
+import subprocess
+
+# Run a shell, source .bashrc, then printenv
+cmd = 'bash -c "source ~/.bashrc && printenv"'
+result = subprocess.run(cmd, shell=True, text=True, capture_output=True)
+env_vars = {}
+for line in result.stdout.splitlines():
+ if '=' in line:
+ key, value = line.split('=', 1)
+ env_vars[key] = value
+
+# Now, update os.environ with these
+os.environ.update(env_vars)
+
+import sys
+from pathlib import Path
+sys.path.append(os.path.abspath("../../elementary/transmission_gate"))
+
+from transmission_gate import transmission_gate, add_tg_labels, get_component_netlist, tg_netlist
+
+def add_cswitch_labels(
+ cswitch_in: Component,
+ pdk: MappedPDK,
+ ) -> Component:
+
+ cswitch_in.unlock()
+
+ psize=(0.5,0.5)
+ # list that will contain all port/comp info
+ move_info = list()
+ # create labels and append to info list
+
+ # VSS
+ vsslabel = rectangle(layer=pdk.get_glayer("met3_pin"),size=psize,centered=True).copy()
+ vsslabel.add_label(text="VSS",layer=pdk.get_glayer("met3_label"))
+ move_info.append((vsslabel,cswitch_in.ports["VSS_TOP_top_met_N"],None))
+ move_info.append((vsslabel,cswitch_in.ports["VSS_BOTTOM_top_met_N"],None))
+ #gnd_ref = top_level << gndlabel;
+
+ #suply
+ vddlabel = rectangle(layer=pdk.get_glayer("met3_pin"),size=psize,centered=True).copy()
+ vddlabel.add_label(text="VDD",layer=pdk.get_glayer("met3_pin"))
+ move_info.append((vddlabel,cswitch_in.ports["VDD_TOPL_top_met_N"],None))
+ move_info.append((vddlabel,cswitch_in.ports["VDD_TOPR_top_met_N"],None))
+ move_info.append((vddlabel,cswitch_in.ports["VDD_BOTTOML_top_met_S"],None))
+ move_info.append((vddlabel,cswitch_in.ports["VDD_BOTTOMR_top_met_S"],None))
+ #sup_ref = top_level << suplabel;
+
+ # output
+ outputplabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=psize,centered=True).copy()
+ outputplabel.add_label(text="VOUTP",layer=pdk.get_glayer("met2_pin"))
+ move_info.append((outputplabel,cswitch_in.ports["OUTP_top_met_E"],None))
+ outputnlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=psize,centered=True).copy()
+ outputnlabel.add_label(text="VOUTN",layer=pdk.get_glayer("met2_pin"))
+ move_info.append((outputnlabel,cswitch_in.ports["OUTN_top_met_E"],None))
+ #op_ref = top_level << outputlabel;
+
+ # input
+ inputplabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=psize,centered=True).copy()
+ inputplabel.add_label(text="VINN",layer=pdk.get_glayer("met2_pin"))
+ move_info.append((inputplabel,cswitch_in.ports["INP_top_met_W"], None))
+ inputnlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=psize,centered=True).copy()
+ inputnlabel.add_label(text="VINP",layer=pdk.get_glayer("met2_pin"))
+ move_info.append((inputnlabel,cswitch_in.ports["INN_top_met_W"], None))
+ #ip_ref = top_level << inputlabel;
+
+ # CLK
+ clklabel = rectangle(layer=pdk.get_glayer("met3_pin"),size=psize,centered=True).copy()
+ clklabel.add_label(text="CLK",layer=pdk.get_glayer("met3_pin"))
+ move_info.append((clklabel,cswitch_in.ports["CLK_TOP_top_met_N"], None))
+ move_info.append((clklabel,cswitch_in.ports["CLK_BOTTOM_top_met_S"], None))
+ clkinvlabel = rectangle(layer=pdk.get_glayer("met3_pin"),size=psize,centered=True).copy()
+ clkinvlabel.add_label(text="CLKINV",layer=pdk.get_glayer("met3_pin"))
+ move_info.append((clkinvlabel,cswitch_in.ports["CLKINV_TOP_top_met_N"], None))
+ move_info.append((clkinvlabel,cswitch_in.ports["CLKINV_BOTTOM_top_met_S"], None))
+ #clk_ref = top_level << clklabel;
+
+ for comp, prt, alignment in move_info:
+ alignment = ('c','b') if alignment is None else alignment
+ compref = align_comp_to_port(comp, prt, alignment=alignment)
+ cswitch_in.add(compref)
+
+ return cswitch_in.flatten()
+
+def cswitch(
+ pdk: MappedPDK,
+ width: tuple[float,float] = (10,10),
+ length: tuple[float,float] = (0.5,0.5),
+ fingers: tuple[int,int] = (6,6),
+ multipliers: tuple[int,int] = (1,1),
+ dummy_1: tuple[bool,bool] = (True,True),
+ dummy_2: tuple[bool,bool] = (True,True),
+ tie_layers1: tuple[str,str] = ("met2","met1"),
+ tie_layers2: tuple[str,str] = ("met2","met1"),
+ sd_rmult: int=1,
+ **kwargs
+ ) -> Component:
+
+ pdk.activate()
+
+ #top level component
+ top_level = Component(name="cswitch")
+
+ #four TG Switch
+ SW1 = transmission_gate(pdk, "vertical",(width[0],width[1]),(length[0],length[1]),(fingers[0],fingers[1]),(multipliers[0],multipliers[1]))
+ SW2 = transmission_gate(pdk, "vertical_invert",(width[0],width[1]),(length[0],length[1]),(fingers[0],fingers[1]),(multipliers[0],multipliers[1]))
+ SW3 = transmission_gate(pdk, "vertical",(width[0],width[1]),(length[0],length[1]),(fingers[0],fingers[1]),(multipliers[0],multipliers[1]))
+ SW4 = transmission_gate(pdk, "vertical_invert",(width[0],width[1]),(length[0],length[1]),(fingers[0],fingers[1]),(multipliers[0],multipliers[1]))
+
+ SW1_ref = top_level << SW1
+ SW2_ref = top_level << SW2
+ SW3_ref = top_level << SW3
+ SW4_ref = top_level << SW4
+
+ SW1_ref.name = "SW1"
+ SW2_ref.name = "SW2"
+ SW3_ref.name = "SW3"
+ SW4_ref.name = "SW4"
+
+ x_distance = 15
+ y_distance = 14.5
+ ref_dimensions = evaluate_bbox(SW1_ref)
+
+ SW2_ref.movex(SW1_ref.xmax + x_distance)
+ SW3_ref.movey(SW1_ref.ymin - y_distance)
+ SW4_ref.movex(SW1_ref.xmax + x_distance).movey(SW1_ref.ymin - y_distance)
+
+ viam2m3 = via_stack(pdk, "met2", "met3", centered=True) #met2 is the bottom layer. met3 is the top layer.
+
+ #via for input and output
+ vinp_start_via = top_level << viam2m3
+ vinp_end_via = top_level << viam2m3
+ vinn_start_via = top_level << viam2m3
+ vinn_end_via = top_level << viam2m3
+
+ voutp_start_via = top_level << viam2m3
+ voutp_end_via = top_level << viam2m3
+ voutn_start_via = top_level << viam2m3
+ voutn_end_via = top_level << viam2m3
+
+ #via for CLK and CLKinv
+ clk_top_via = top_level << viam2m3
+ clk_bottom_via = top_level << viam2m3
+ clkinv_top_via = top_level << viam2m3
+ clkinv_bottom_via = top_level << viam2m3
+
+
+ top_level << straight_route(pdk, SW1_ref.ports["P_gate_E"], SW2_ref.ports["N_gate_W"])
+ top_level << straight_route(pdk, SW1_ref.ports["N_gate_E"], SW2_ref.ports["P_gate_W"])
+ top_level << straight_route(pdk, SW3_ref.ports["P_gate_E"], SW4_ref.ports["N_gate_W"])
+ top_level << straight_route(pdk, SW3_ref.ports["N_gate_E"], SW4_ref.ports["P_gate_W"])
+
+ vinp_start_via.move(SW1_ref.ports["P_source_top_met_W"].center).movey(SW1_ref.ymin - pdk.util_max_metal_seperation()).movex(-ref_dimensions[0]/1.55)
+ vinn_start_via.move(vinp_start_via.center).movey(- 0.75 - pdk.util_max_metal_seperation())
+ vinp_end_via.move(vinp_start_via.center).movex(SW2_ref.xmax - x_distance/2)
+ vinn_end_via.move(vinn_start_via.center).movex(SW2_ref.xmax - x_distance/2 - 1)
+
+ voutp_start_via.move(vinp_start_via.center).movey(pdk.util_max_metal_seperation()+0.75).movex(SW2_ref.xmin - 4)
+ voutn_start_via.move(vinn_start_via.center).movey(-pdk.util_max_metal_seperation()-0.75).movex(SW2_ref.xmin - 4)
+ voutp_end_via.move(voutp_start_via.center).movex(SW2_ref.xmax-6)
+ voutn_end_via.move(voutn_start_via.center).movex(SW2_ref.xmax-7)
+
+ clk_top_via.move(SW1_ref.ports["P_gate_E"].center).movex(SW1_ref.xmax-1.5)
+ clkinv_top_via.move(SW2_ref.ports["P_gate_W"].center).movex(SW1_ref.xmin+1.5)
+ clk_bottom_via.move(SW3_ref.ports["P_gate_E"].center).movex(SW1_ref.xmax-1.5)
+ clkinv_bottom_via.move(SW4_ref.ports["P_gate_W"].center).movex(SW1_ref.xmin+1.5)
+
+ #input routes
+ top_level << L_route(pdk, SW1_ref.ports["P_drain_top_met_W"], vinp_start_via.ports["top_met_N"])
+ top_level << L_route(pdk, SW3_ref.ports["P_drain_top_met_W"], vinn_start_via.ports["top_met_S"])
+ top_level << L_route(pdk, SW4_ref.ports["P_drain_top_met_W"], vinn_end_via.ports["top_met_N"])
+ top_level << L_route(pdk, SW2_ref.ports["P_drain_top_met_W"], vinp_end_via.ports["top_met_S"])
+ top_level << straight_route(pdk, vinp_start_via.ports["bottom_met_E"], vinp_end_via.ports["bottom_met_W"])
+ top_level << straight_route(pdk, vinn_start_via.ports["bottom_met_E"], vinn_end_via.ports["bottom_met_W"])
+
+ #output routes
+ top_level << L_route(pdk, SW1_ref.ports["P_source_top_met_E"], voutp_start_via.ports["top_met_N"])
+ top_level << L_route(pdk, SW3_ref.ports["P_source_top_met_E"], voutn_start_via.ports["top_met_S"])
+ top_level << L_route(pdk, SW2_ref.ports["P_source_top_met_E"], voutn_end_via.ports["top_met_N"])
+ top_level << L_route(pdk, SW4_ref.ports["P_source_top_met_E"], voutp_end_via.ports["top_met_S"])
+ top_level << straight_route(pdk, voutp_start_via.ports["bottom_met_E"], voutp_end_via.ports["bottom_met_W"])
+ top_level << straight_route(pdk, voutn_start_via.ports["bottom_met_E"], voutn_end_via.ports["bottom_met_W"])
+
+ #CLK routes
+ top_level << straight_route(pdk, clk_top_via.ports["top_met_S"], clk_bottom_via.ports["top_met_N"])
+ top_level << straight_route(pdk, clkinv_top_via.ports["top_met_S"], clkinv_bottom_via.ports["top_met_N"])
+
+ # Add tapring
+ tap_ring = tapring(pdk, enclosed_rectangle=evaluate_bbox(top_level.flatten(), padding=pdk.get_grule("nwell", "active_diff")["min_enclosure"]+pdk.util_max_metal_seperation()))
+ shift_amount = -prec_center(top_level.flatten())[0]
+ shifty_amount = prec_center(top_level.flatten())[1]
+ tring_ref = top_level << tap_ring
+ tring_ref.movex(destination=shift_amount).movey(destination=-shifty_amount)
+
+ #VDD Rails
+ viaarray = via_array(pdk, "met2", "met3", (2,1))
+
+ VDD1_via = top_level << viaarray
+ VDD2_via = top_level << viaarray
+ VDD3_via = top_level << viaarray
+ VDD4_via = top_level << viaarray
+ VDD5_via = top_level << viaarray
+ VDD6_via = top_level << viaarray
+ VDD7_via = top_level << viaarray
+ VDD8_via = top_level << viaarray
+
+ VDD1_via.move(SW1_ref.ports["P_tie_N_top_met_E"].center).movex(SW1_ref.xmin+0.75)
+ VDD2_via.move(SW1_ref.ports["P_tie_S_top_met_E"].center).movex(SW1_ref.xmin+0.75)
+ VDD3_via.move(SW2_ref.ports["P_tie_N_top_met_E"].center).movex(SW1_ref.xmin+0.75)
+ VDD4_via.move(SW2_ref.ports["P_tie_S_top_met_E"].center).movex(SW1_ref.xmin+0.75)
+ VDD5_via.move(SW3_ref.ports["P_tie_N_top_met_E"].center).movex(SW1_ref.xmin+0.75)
+ VDD6_via.move(SW3_ref.ports["P_tie_S_top_met_E"].center).movex(SW1_ref.xmin+0.75)
+ VDD7_via.move(SW4_ref.ports["P_tie_N_top_met_E"].center).movex(SW1_ref.xmin+0.75)
+ VDD8_via.move(SW4_ref.ports["P_tie_S_top_met_E"].center).movex(SW1_ref.xmin+0.75)
+
+ top_level << straight_route(pdk, VDD1_via.ports["top_met_S"], VDD6_via.ports["top_met_N"])
+ top_level << straight_route(pdk, VDD4_via.ports["top_met_S"], VDD7_via.ports["top_met_N"])
+
+ # VSS Rails
+ VSS1_via = top_level << viaarray
+ VSS2_via = top_level << viaarray
+ VSS1_via.move(tring_ref.ports["N_top_met_E"].center).movex(tring_ref.xmin*2.25)
+ VSS2_via.move(tring_ref.ports["S_top_met_E"].center).movex(tring_ref.xmin*2.25)
+
+ top_level << straight_route(pdk, VSS1_via.ports["top_met_N"], VSS2_via.ports["top_met_S"], width=2)
+
+ top_level << straight_route(pdk, SW2_ref.ports["N_tie_N_top_met_W"], VSS1_via.ports["top_met_N"])
+ top_level << straight_route(pdk, SW2_ref.ports["N_tie_S_top_met_W"], VSS1_via.ports["top_met_N"])
+ top_level << straight_route(pdk, SW1_ref.ports["N_tie_N_top_met_E"], VSS1_via.ports["top_met_N"])
+ top_level << straight_route(pdk, SW1_ref.ports["N_tie_S_top_met_E"], VSS1_via.ports["top_met_N"])
+ top_level << straight_route(pdk, SW4_ref.ports["N_tie_N_top_met_W"], VSS1_via.ports["top_met_N"])
+ top_level << straight_route(pdk, SW4_ref.ports["N_tie_S_top_met_W"], VSS1_via.ports["top_met_N"])
+ top_level << straight_route(pdk, SW3_ref.ports["N_tie_N_top_met_E"], VSS1_via.ports["top_met_N"])
+ top_level << straight_route(pdk, SW3_ref.ports["N_tie_S_top_met_E"], VSS1_via.ports["top_met_N"])
+
+ viam1m2 = via_stack(pdk, "met1", "met2", centered=True) #met2 is the bottom layer. met3 is the top layer.
+
+ # tapring - bulk pmos
+ SW1_tapring = top_level << viam1m2
+ SW2_tapring = top_level << viam1m2
+ SW3_tapring = top_level << viam1m2
+ SW4_tapring = top_level << viam1m2
+
+ SW1_tapring.move(SW1_ref.ports["N_tie_W_array_row25_col0_top_met_W"].center).movex(-1.2)
+ SW2_tapring.move(SW2_ref.ports["N_tie_E_array_row25_col0_top_met_W"].center).movex(1.6)
+ SW3_tapring.move(SW3_ref.ports["N_tie_W_array_row25_col0_top_met_W"].center).movex(-1.2)
+ SW4_tapring.move(SW4_ref.ports["N_tie_E_array_row25_col0_top_met_W"].center).movex(1.6)
+
+ top_level << straight_route(pdk, SW1_tapring.ports["top_met_W"], SW1_ref.ports["N_tie_W_array_row25_col0_top_met_E"])
+ top_level << straight_route(pdk, SW2_tapring.ports["top_met_E"], SW2_ref.ports["N_tie_E_array_row25_col0_top_met_W"])
+ top_level << straight_route(pdk, SW3_tapring.ports["top_met_W"], SW3_ref.ports["N_tie_W_array_row25_col0_top_met_E"])
+ top_level << straight_route(pdk, SW4_tapring.ports["top_met_E"], SW4_ref.ports["N_tie_E_array_row25_col0_top_met_W"])
+
+ top_level.add_ports(SW1_ref.get_ports_list(), prefix="SW1_")
+ top_level.add_ports(SW2_ref.get_ports_list(), prefix="SW2_")
+ top_level.add_ports(SW3_ref.get_ports_list(), prefix="SW3_")
+ top_level.add_ports(SW4_ref.get_ports_list(), prefix="SW4_")
+
+ top_level.add_ports(vinp_start_via.get_ports_list(), prefix="INP_")
+ top_level.add_ports(vinn_start_via.get_ports_list(), prefix="INN_")
+ top_level.add_ports(voutp_end_via.get_ports_list(), prefix="OUTP_")
+ top_level.add_ports(voutn_end_via.get_ports_list(), prefix="OUTN_")
+
+ top_level.add_ports(clk_top_via.get_ports_list(), prefix="CLK_TOP_")
+ top_level.add_ports(clkinv_top_via.get_ports_list(), prefix="CLKINV_TOP_")
+ top_level.add_ports(clk_bottom_via.get_ports_list(), prefix="CLK_BOTTOM_")
+ top_level.add_ports(clkinv_bottom_via.get_ports_list(), prefix="CLKINV_BOTTOM_")
+
+ top_level.add_ports(VDD1_via.get_ports_list(), prefix="VDD_TOPL_")
+ top_level.add_ports(VDD3_via.get_ports_list(), prefix="VDD_TOPR_")
+ top_level.add_ports(VDD6_via.get_ports_list(), prefix="VDD_BOTTOML_")
+ top_level.add_ports(VDD8_via.get_ports_list(), prefix="VDD_BOTTOMR_")
+
+ top_level.add_ports(VSS1_via.get_ports_list(), prefix="VSS_TOP_")
+ top_level.add_ports(VSS2_via.get_ports_list(), prefix="VSS_BOTTOM_")
+
+ return component_snap_to_grid(rename_ports_by_orientation(top_level))
+
+if __name__ == "__main__":
+ comp = cswitch(gf180)
+
+ # comp.pprint_ports()
+
+ comp = add_cswitch_labels(comp, gf180)
+
+ comp.name = "CSWITCH"
+
+ comp.write_gds('out_CSWITCH.gds')
+
+ comp.show()
+
+ print("...Running DRC...")
+
+ drc_result = gf180.drc_magic(comp, "CSWITCH")
+
+ drc_result = gf180.drc(comp)
+
diff --git a/src/glayout/cells/composite/instrumentation_amplifier/__init__.py b/src/glayout/cells/composite/instrumentation_amplifier/__init__.py
new file mode 100644
index 00000000..a374f973
--- /dev/null
+++ b/src/glayout/cells/composite/instrumentation_amplifier/__init__.py
@@ -0,0 +1,10 @@
+
+###Glayout instrumentation amplifier Cell.
+
+
+from .instrumentation_amplifier import generate_ina, add_amplifier_labels
+
+__all__ = [
+ 'generate_ina',
+ 'add_amplifier_labels',
+]
diff --git a/src/glayout/cells/composite/instrumentation_amplifier/instrumentation_amplifier.md b/src/glayout/cells/composite/instrumentation_amplifier/instrumentation_amplifier.md
new file mode 100644
index 00000000..f93a074a
--- /dev/null
+++ b/src/glayout/cells/composite/instrumentation_amplifier/instrumentation_amplifier.md
@@ -0,0 +1,122 @@
+# Instrumentation Amplifier
+This amplifier uses an open-loop differential architecture to achieve high voltage gain with a relatively simple structure and a small number of transistors.
+
+
+
+
+### Differential Input Pair
+Transistors PM1 and PM2 form the differential input pair, which receives the input signals VIN+ and VIN−. They convert the input voltage difference into differential current.
+
+### Gain Stage / Active Load
+Transistors NM1, NM2, NM3, and NM4 act as the active load and gain stage, converting the differential current into output voltages at VOUT+ and VOUT− while maintaining differential operation.
+
+**dnwell must be added manually to nmos pair
+
+### Biasing Transistor
+Transistor PM3 provides the bias current for the amplifier. The current is controlled by a bias voltage (Vbias) applied to its gate (VB), meaning the circuit uses voltage biasing instead of an Ibias current source.
+
+### Differential Output
+The amplifier produces differential outputs, VOUT+ and VOUT−, which represent the amplified difference between the input signals.
+
+
+## Parameterization
+Configuration : Contains the PMOS and NMOS device parameters used in the amplifier, including transistor width, length,
+ finger count, multipliers, dummy devices, and metal tie layers, which define the sizing and layout
+ configuration of each transistor block.
+
+x_distance : Sets the horizontal separation between the left and right sides of the differential pair.
+
+row_gap : Defines the vertical spacing between different transistor rows.
+
+bias_gap : Defines the vertical spacing between the bias block and the main circuit.
+
+trunk_pitch_in: Controls the pitch (spacing/width) of the vertical routing trunks for the input signals.
+
+trunk_pitch_out: Controls the pitch of the vertical routing trunks for the output signals.
+
+outpad_margin : Sets the spacing buffer between the core amplifier layout and the I/O pads or chip boundary.
+
+outer_keepout : Defines the clearance distance for the outer guard ring.
+
+nmos_pair_outer_ring_padding: Sets the spacing between the NMOS differential pair and the outer guard ring.
+
+bias_gate_route_dx: Specifies the horizontal extension used to route the VBIAS connection.
+
+c_route_extension: Defines the extension length for the common routing path.
+
+vcm_dx : Sets the horizontal offset for the VCM (common-mode voltage) connection.
+
+vin_dx : Specifies the horizontal routing extension for the input connections.
+
+vout_dx : Specifies the horizontal routing extension for the output connections.
+
+```
+def generate_ina(
+ pdk: MappedPDK,
+ Configuration: Dict[str,Any] = None,
+ x_distance: int = 5,
+ row_gap: float = 6.0,
+ bias_gap: float = 4.0,
+ trunk_pitch_in: float =3.4,
+ trunk_pitch_out: float =1.0,
+ outpad_margin: float = 35.0,
+ outer_keepout: float = 6.0,
+ nmos_pair_outer_ring_padding: float = 2.2,
+ bias_gate_route_dx: float = 25.0,
+ c_route_extension: float = 6.0,
+ vcm_dx: float = -20.0,
+ vin_dx: float = 40.0,
+ vout_dx: float = 40.0,
+ nmos_kwargs: Dict[str,Any] = None,
+ pmos_kwargs: Dict[str,Any] = None,
+ **kwargs
+ ) -> Component:
+```
+Example of how to define Configuration:
+```
+CFG = {
+ "pmos_pair": {"pdk": gf180, "placement": "horizontal", "width": (6, 6), "length": (3, 3), "fingers": (6, 6), "multipliers": (1, 1), "dummy_1": (True, True), "dummy_2": (True, True), "tie_layers1": ("met2", "met1"), "tie_layers2": ("met2", "met1"), "sd_rmult": 1},
+ "pmos_bias": {"pdk": gf180, "placement": "horizontal", "width": (0.5, 0.5), "length": (6.9, 6.9), "fingers": (1, 1), "multipliers": (1, 1), "dummy_1": (True, True), "dummy_2": (True, True), "tie_layers1": ("met2", "met1"), "tie_layers2": ("met2", "met1"), "sd_rmult": 1},
+ "nmos_pair": {"pdk": gf180, "placement": "horizontal", "width": (3, 3), "length": (3, 3), "fingers": (1, 1), "multipliers": (1, 1), "dummy_1": (True, True), "dummy_2": (True, True), "tie_layers1": ("met2", "met1"), "tie_layers2": ("met2", "met1"), "sd_rmult": 1},
+ "nmos_mirror": {"pdk": gf180, "placement": "horizontal", "width": (0.5, 0.5), "length": (6.9, 6.9), "fingers": (1, 1), "multipliers": (1, 1), "dummy_1": (True, True), "dummy_2": (True, True), "tie_layers1": ("met2", "met1"), "tie_layers2": ("met2", "met1"), "sd_rmult": 1},
+ "pmos_diode": {"pdk": gf180, "placement": "horizontal", "width": (0.5, 0.5), "length": (6.9, 6.9), "fingers": (1, 1), "multipliers": (1, 1), "dummy_1": (True, True), "dummy_2": (True, True), "tie_layers1": ("met2", "met1"), "tie_layers2": ("met2", "met1"), "sd_rmult": 1}
+ }
+```
+
+## Generated GDS
+
+
+## DRC Report
+```
+using default pdk_root
+Defaulting to stale magic_commands.tcl
+
+Magic 8.3 revision 528 - Compiled on Wed Jun 18 09:45:25 PM CEST 2025.
+Starting magic under Tcl interpreter
+Using the terminal as the console.
+WARNING: RLIMIT_NOFILE is above 1024 and Tcl_Version<9 this may cause runtime issues [rlim_cur=1048576]
+Using NULL graphics device.
+Processing system .magicrc file
+Sourcing design .magicrc for technology gf180mcuD ...
+10 Magic internal units = 1 Lambda
+Input style import: scaleFactor=10, multiplier=2
+The following types are not handled by extraction and will be treated as non-electrical types:
+ 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
+Scaled tech values by 10 / 1 to match internal grid scaling
+Loading gf180mcuD Device Generator Menu ...
+Loading "/tmp/tmpu3sxch2e/magic_commands.tcl" from command line.
+Warning: Calma reading is not undoable! I hope that's OK.
+Library written using GDS-II Release 6.0
+Library name: library
+Reading "INA".
+[INFO]: Loading INA
+
+Loading DRC CIF style.
+No errors found.
+[INFO]: DONE with /tmp/tmpu3sxch2e/INA.rpt
+
+Using technology "gf180mcuD", version 1.0.525-0-gf2e289d
+
+{'result_str': 'magic drc script passed\nNo errors found in DRC report',
+ 'subproc_code': 0}
+```
diff --git a/src/glayout/cells/composite/instrumentation_amplifier/instrumentation_amplifier.py b/src/glayout/cells/composite/instrumentation_amplifier/instrumentation_amplifier.py
new file mode 100644
index 00000000..14baec10
--- /dev/null
+++ b/src/glayout/cells/composite/instrumentation_amplifier/instrumentation_amplifier.py
@@ -0,0 +1,474 @@
+# PDK import, components
+from glayout import MappedPDK, sky130 , gf180
+from gdsfactory.cell import cell
+from gdsfactory import Component
+from gdsfactory.components import text_freetype, rectangle
+from typing import Dict, Any, Tuple
+
+import os
+import gdstk
+import svgutils.transform as sg
+import IPython.display
+import ipywidgets as widgets
+from gdsfactory import Component
+from gdsfactory.port import Port
+from gdsfactory.components import rectangle
+from glayout import gf180
+from glayout import nmos, pmos, tapring
+from glayout.util.comp_utils import evaluate_bbox, prec_center, align_comp_to_port
+from glayout import rename_ports_by_orientation
+from glayout.util.port_utils import add_ports_perimeter,print_ports
+from glayout.util.snap_to_grid import component_snap_to_grid
+from glayout.spice.netlist import Netlist
+
+# ROUTING
+from glayout.routing.straight_route import straight_route
+from glayout.routing.c_route import c_route
+
+# VIAS import
+from glayout import via_stack, via_array
+
+import os
+import subprocess
+
+# Run a shell, source .bashrc, then printenv
+cmd = 'bash -c "source ~/.bashrc && printenv"'
+result = subprocess.run(cmd, shell=True, text=True, capture_output=True)
+env_vars = {}
+for line in result.stdout.splitlines():
+ if '=' in line:
+ key, value = line.split('=', 1)
+ env_vars[key] = value
+
+# Now, update os.environ with these
+os.environ.update(env_vars)
+
+def get_component_netlist(component):
+ """Helper function to get netlist object from component info, compatible with all gdsfactory versions"""
+ from glayout.spice.netlist import Netlist
+
+ # Try to get stored object first (for older gdsfactory versions)
+ if 'netlist_obj' in component.info:
+ return component.info['netlist_obj']
+
+ # Try to reconstruct from netlist_data (for newer gdsfactory versions)
+ if 'netlist_data' in component.info:
+ data = component.info['netlist_data']
+ netlist = Netlist(
+ circuit_name=data['circuit_name'],
+ nodes=data['nodes']
+ )
+ netlist.source_netlist = data['source_netlist']
+ return netlist
+
+ # Fallback: return the string representation (should not happen in normal operation)
+ return component.info.get('netlist', '')
+
+def ina_netlist(pmos_left, pmos_right, nmos_left, nmos_right, nmos_mirror_left, nmos_mirror_right, pmos_diode_left, pmos_diode_right, pmos_bias) -> Netlist:
+ pmos_left = get_component_netlist(pmos_left)
+ pmos_right = get_component_netlist(pmos_right)
+ nmos_left = get_component_netlist(nmos_left)
+ nmos_right = get_component_netlist(nmos_right)
+ nmos_mirror_left = get_component_netlist(nmos_mirror_left)
+ nmos_mirror_right = get_component_netlist(nmos_mirror_right)
+ pmos_diode_left = get_component_netlist(pmos_diode_left)
+ pmos_diode_right = get_component_netlist(pmos_diode_right)
+ pmos_bias = get_component_netlist(pmos_bias)
+
+ netlist = Netlist(circuit_name="instrumentation_amplifier", nodes=['VOUT', 'VIN', 'VSS', 'VDD', 'VTAIL', 'VCM', 'VBIAS'])
+ netlist.connect_netlist(pmos_diode_left, [('D', 'VCM'), ('G', 'VCM'), ('S', 'VINP'), ('B', 'VINP')])
+ netlist.connect_netlist(pmos_diode_right, [('D', 'VCM'), ('G', 'VCM'), ('S', 'VOUTP'), ('B', 'VOUTP')])
+ netlist.connect_netlist(nmos_mirror_left, [('D', 'VSS'), ('G', 'VOUTP'), ('S', 'VSS'), ('B', 'VSS')])
+ netlist.connect_netlist(nmos_mirror_right, [('D', 'VSS'), ('G', 'VOUTN'), ('S', 'VSS'), ('B', 'VSS')])
+ netlist.connect_netlist(nmos_left, [('D', 'VOUTP'), ('G', 'VINP'), ('S', 'VSS'), ('B', 'VSS')])
+ netlist.connect_netlist(nmos_right, [('D', 'VOUTN'), ('G', 'VINN'), ('S', 'VSS'), ('B', 'VSS')])
+ netlist.connect_netlist(pmos_left, [('D', 'VOUTP'), ('G', 'VINP'), ('S', 'VTAIL'), ('B', 'VTAIL')])
+ netlist.connect_netlist(pmos_right, [('D', 'VOUTN'), ('G', 'VINN'), ('S', 'VTAIL'), ('B', 'VTAIL')])
+ netlist.connect_netlist(pmos_bias, [('D', 'VTAIL'), ('G', 'VBIAS'), ('S', 'VDD'), ('B', 'VDD')])
+
+ return netlist
+
+def bbox_wh(obj, padding=0.0):
+ b = evaluate_bbox(obj, padding=padding)
+ if b is None: raise ValueError("evaluate_bbox returned None")
+ if len(b) == 2: return float(b[0]), float(b[1])
+ if len(b) == 4: return float(b[2] - b[0]), float(b[3] - b[1])
+ raise ValueError(f"unexpected bbox format: {b}")
+
+# make tapring (automatic) for every pairs
+def safe_tapring(pdk, obj, padding=0.6, sdlayer=None, **kwargs):
+ w, h = bbox_wh(obj, padding=padding)
+ min_gap_tap = pdk.get_grule("active_tap")["min_separation"]
+ w = max(w, min_gap_tap + 0.05)
+ h = max(h, min_gap_tap + 0.05)
+ if sdlayer is not None:
+ return tapring(pdk, enclosed_rectangle=(w, h), sdlayer=sdlayer, **kwargs)
+ return tapring(pdk, enclosed_rectangle=(w, h), **kwargs)
+
+# point reference to object for automatic routing (can be used)
+def center_ref_to_obj(ref, obj):
+ cx, cy = prec_center(obj)
+ ref.movex(destination=-cx).movey(destination=-cy)
+ return ref
+# add ports for layouting
+def export_ports_by_suffix(comp, ref, prefix):
+ def add(newname, suffix):
+ for k, p in ref.ports.items():
+ if k.endswith(suffix):
+ name = f"{prefix}_{newname}"
+ if name not in comp.ports: comp.add_port(name, port=p)
+ return
+ add("gate_E", "gate_E"); add("gate_W", "gate_W")
+ add("source_E", "source_E"); add("source_W", "source_W"); add("source_N", "source_N")
+ add("drain_E", "drain_E"); add("drain_W", "drain_W"); add("drain_N", "drain_N"); add("drain_S", "drain_S")
+
+# function to automatic route only if the two point that want to be connected is parallel. If not, this won't work,
+def route_if_ports_exist(pdk, comp, refA, keyA, refB, keyB):
+ if (keyA in refA.ports) and (keyB in refB.ports):
+ comp << straight_route(pdk, refA.ports[keyA], refB.ports[keyB])
+
+# BUILD PAIR BLOCK
+def build_pair_block(name, pdk, fet_fun, cfg, kwargs, x_distance=5, tap=True, tap_padding=0.6, dnwell=False, export_ports=True, individual_tap=False):
+ w, l, f, m = cfg["width"], cfg["length"], cfg["fingers"], cfg["multipliers"]
+ dummy, tie, sd_rmult = cfg["dummy_1"], cfg["tie_layers1"], cfg["sd_rmult"]
+ local_kwargs = dict(kwargs)
+ if fet_fun == nmos: local_kwargs["with_dnwell"] = False
+ manual_assembly = dnwell and individual_tap
+
+ # Nested fet : helped if dnwell is needed and automatically centered to the NMOS for each pair
+ def create_nested_fet_block(block_name, fet_width, fet_length, fet_fingers, fet_mult):
+ sub_comp = Component(block_name)
+ fet_ref = sub_comp << fet_fun(pdk, width=fet_width, length=fet_length, fingers=fet_fingers, multipliers=fet_mult, with_dummy=dummy, with_substrate_tap=False if manual_assembly else individual_tap, tie_layers=tie, sd_rmult=sd_rmult, **local_kwargs)
+ sub_comp.add_ports(fet_ref.ports)
+ if manual_assembly:
+ inner_ring = safe_tapring(pdk, fet_ref, padding=0.6, sdlayer="n+s/d")
+ inner_ref = sub_comp << inner_ring
+ center_ref_to_obj(inner_ref, fet_ref)
+ wb, hb = bbox_wh(inner_ring, padding=1.0)
+ poly_dnwell = sub_comp.add_polygon([(-wb/2, -hb/2), (wb/2, -hb/2), (wb/2, hb/2), (-wb/2, hb/2)], layer=pdk.get_glayer("dnwell"))
+ cx, cy = prec_center(fet_ref); poly_dnwell.move((cx, cy))
+ return sub_comp
+
+ comp = Component(name)
+ block1 = create_nested_fet_block(f"{name}_blk1", w[0], l[0], f[0], m[0])
+ r1 = comp << block1; r1.name = f"{name}_1"
+ block2 = create_nested_fet_block(f"{name}_blk2", w[1], l[1], f[1], m[1])
+ r2 = comp << block2; r2.name = f"{name}_2"
+ comp.info["fets"] = {
+ "m1": r1,
+ "m2": r2
+ }
+ compSep = gf180.util_max_metal_seperation()
+ if cfg["placement"] == "horizontal": w1, _ = bbox_wh(block1, padding=0.0); r2.movex(compSep + w1 + x_distance)
+ else: raise ValueError("placement must be horizontal")
+
+ # make the tapring based on the safe_tapring function
+ if tap:
+ flat = comp.flatten(); ring = safe_tapring(pdk, flat, padding=tap_padding)
+ ring_ref = comp << ring; center_ref_to_obj(ring_ref, flat)
+ if not manual_assembly and not individual_tap:
+ if "source_N" in r1.ports and "top_met_S" in ring_ref.ports: comp << straight_route(pdk, r1.ports["source_N"], ring_ref.ports["top_met_S"])
+ if "source_N" in r2.ports and "top_met_S" in ring_ref.ports: comp << straight_route(pdk, r2.ports["source_N"], ring_ref.ports["top_met_S"])
+
+ flat2 = comp.flatten(); cx, cy = prec_center(flat2)
+ tie_pairs = []
+ if name in ["NMOS_PAIR_INNER"]:
+ tie_pairs = [("tie_S_array_row0_col0_top_met_E", "tie_S_array_row0_col0_top_met_W"), ("tie_N_array_row0_col0_top_met_E", "tie_N_array_row0_col0_top_met_W")]
+ for k1, k2 in tie_pairs: route_if_ports_exist(pdk, comp, r1, k1, r2, k2)
+
+ if ("multiplier_0_drain_N" in r1.ports) and ("tie_N_top_met_N" in r1.ports): comp << straight_route(pdk, r1.ports["multiplier_0_drain_N"], r1.ports["tie_N_top_met_N"])
+ if ("multiplier_0_drain_N" in r2.ports) and ("tie_N_top_met_N" in r2.ports): comp << straight_route(pdk, r2.ports["multiplier_0_drain_N"], r2.ports["tie_N_top_met_N"])
+
+ for rr in comp.references: rr.movex(-cx).movey(-cy)
+ for poly in comp.polygons: poly.move((-cx, -cy))
+ if export_ports: export_ports_by_suffix(comp, r1, "M1"); export_ports_by_suffix(comp, r2, "M2")
+ return comp
+
+
+def add_amplifier_labels(
+ ina: Component,
+ pdk: MappedPDK,
+ ) -> Component:
+
+ ina.unlock()
+
+ psize = (0.5, 0.5)
+ move_info = list()
+
+ # Output
+ outputplabel = rectangle(layer=pdk.get_glayer("met2_pin"), size=psize, centered=True).copy()
+ outputplabel.add_label(text="VOUTP", layer=pdk.get_glayer("met2_pin"))
+ move_info.append((outputplabel, ina.ports["OUTP_top_met_E"], None))
+
+ outputnlabel = rectangle(layer=pdk.get_glayer("met2_pin"), size=psize, centered=True).copy()
+ outputnlabel.add_label(text="VOUTN", layer=pdk.get_glayer("met2_pin"))
+ move_info.append((outputnlabel, ina.ports["OUTN_top_met_E"], None))
+
+ # Input
+ inputplabel = rectangle(layer=pdk.get_glayer("met2_pin"), size=psize, centered=True).copy()
+ inputplabel.add_label(text="VINP", layer=pdk.get_glayer("met2_pin"))
+ move_info.append((inputplabel, ina.ports["INP_top_met_W"], None))
+
+ inputnlabel = rectangle(layer=pdk.get_glayer("met2_pin"), size=psize, centered=True).copy()
+ inputnlabel.add_label(text="VINN", layer=pdk.get_glayer("met2_pin"))
+ move_info.append((inputnlabel, ina.ports["INN_top_met_W"], None))
+
+ # Apply Labels (Standard Move)
+ for comp, prt, alignment in move_info:
+ alignment = ('c', 'b') if alignment is None else alignment
+ compref = align_comp_to_port(comp, prt, alignment=alignment)
+ ina.add(compref)
+
+ return ina.flatten()
+
+def deep_update(default, user_input):
+ if user_input is None:
+ return default
+
+ # Salin default agar tidak merubah data asli
+ res = default.copy()
+
+ for k, v in user_input.items():
+ # Jika nilai adalah dictionary dan k ada di res, lakukan update dalam
+ if isinstance(v, dict) and k in res and isinstance(res[k], dict):
+ res[k] = deep_update(res[k], v)
+ else:
+ res[k] = v
+ return res
+
+
+@cell
+def generate_ina(
+ pdk: MappedPDK,
+ CFG: Dict[str,Any] = None,
+ x_distance: int = 5,
+ row_gap: float = 6.0,
+ bias_gap: float = 4.0,
+ trunk_pitch_in: float =3.4,
+ trunk_pitch_out: float =1.0,
+ outpad_margin: float = 35.0,
+ outer_keepout: float = 6.0,
+ nmos_pair_outer_ring_padding: float = 2.2,
+ bias_gate_route_dx: float = 25.0,
+ c_route_extension: float = 6.0,
+ vcm_dx: float = -20.0,
+ vin_dx: float = 40.0,
+ vout_dx: float = 40.0,
+ nmos_kwargs: Dict[str,Any] = None,
+ pmos_kwargs: Dict[str,Any] = None,
+ **kwargs
+ ) -> Component:
+
+ pdk.activate()
+
+ DEFAULT_CFG = {
+ "pmos_pair": {"pdk": gf180, "placement": "horizontal", "width": (6, 6), "length": (3, 3), "fingers": (6, 6), "multipliers": (1, 1), "dummy_1": (True, True), "dummy_2": (True, True), "tie_layers1": ("met2", "met1"), "tie_layers2": ("met2", "met1"), "sd_rmult": 1},
+ "pmos_bias": {"pdk": gf180, "placement": "horizontal", "width": (0.5, 0.5), "length": (6.9, 6.9), "fingers": (1, 1), "multipliers": (1, 1), "dummy_1": (True, True), "dummy_2": (True, True), "tie_layers1": ("met2", "met1"), "tie_layers2": ("met2", "met1"), "sd_rmult": 1},
+ "nmos_pair": {"pdk": gf180, "placement": "horizontal", "width": (3, 3), "length": (3, 3), "fingers": (1, 1), "multipliers": (1, 1), "dummy_1": (True, True), "dummy_2": (True, True), "tie_layers1": ("met2", "met1"), "tie_layers2": ("met2", "met1"), "sd_rmult": 1},
+ "nmos_mirror": {"pdk": gf180, "placement": "horizontal", "width": (0.5, 0.5), "length": (6.9, 6.9), "fingers": (1, 1), "multipliers": (1, 1), "dummy_1": (True, True), "dummy_2": (True, True), "tie_layers1": ("met2", "met1"), "tie_layers2": ("met2", "met1"), "sd_rmult": 1},
+ "pmos_diode": {"pdk": gf180, "placement": "horizontal", "width": (0.5, 0.5), "length": (6.9, 6.9), "fingers": (1, 1), "multipliers": (1, 1), "dummy_1": (True, True), "dummy_2": (True, True), "tie_layers1": ("met2", "met1"), "tie_layers2": ("met2", "met1"), "sd_rmult": 1}
+ }
+
+ DEFAULT_NMOS = {
+ "with_tie": True, "with_dnwell": False, "sd_route_topmet": "met2", "gate_route_topmet": "met2",
+ "sd_route_left": True, "rmult": None, "gate_rmult": 1, "interfinger_rmult": 1,
+ "substrate_tap_layers": ("met2", "met1"), "dummy_routes": True
+ }
+
+ DEFAULT_PMOS = {
+ "with_tie": True, "dnwell": False, "sd_route_topmet": "met2", "gate_route_topmet": "met2",
+ "sd_route_left": True, "rmult": None, "gate_rmult": 1, "interfinger_rmult": 1,
+ "substrate_tap_layers": ("met2", "met1"), "dummy_routes": True
+ }
+
+ # 2. Proses Merging (Update otomatis)
+ cfg = deep_update(DEFAULT_CFG, CFG or {})
+ nmos_kwargs = deep_update(DEFAULT_NMOS, nmos_kwargs or {})
+ pmos_kwargs = deep_update(DEFAULT_PMOS, pmos_kwargs or {})
+
+ PMOS_PAIR = build_pair_block("PMOS_PAIR", pdk, pmos, cfg["pmos_pair"], pmos_kwargs, x_distance=x_distance, tap=True, tap_padding=0.6, export_ports=True)
+ NMOS_PAIR_INNER = build_pair_block("NMOS_PAIR_INNER", pdk, nmos, cfg["nmos_pair"], nmos_kwargs, x_distance=x_distance, tap=True, individual_tap=False, dnwell=True, tap_padding=nmos_pair_outer_ring_padding, export_ports=True)
+ NMOS_PAIR = Component("NMOS_PAIR"); nmos_pair_inner_ref = NMOS_PAIR << NMOS_PAIR_INNER; nmos_pair_inner_ref.move((0, 0))
+ for pname, p in nmos_pair_inner_ref.ports.items():
+ if pname not in NMOS_PAIR.ports: NMOS_PAIR.add_port(pname, port=p)
+ NMOS_MIRROR = build_pair_block("NMOS_MIRROR", pdk, nmos, cfg["nmos_mirror"], nmos_kwargs, x_distance=x_distance, tap=True, individual_tap=False, dnwell=False, tap_padding=0.6, export_ports=True)
+ PMOS_DIODE = build_pair_block("PMOS_DIODE", pdk, pmos, cfg["pmos_diode"], {}, x_distance=x_distance, tap=True, tap_padding=0.6, export_ports=True)
+
+ ALL_TOP = Component(name ="ALL_TOP")
+ TOP = ALL_TOP
+ X_TRUNK = 0.0
+
+ bias = pmos(pdk, width=cfg["pmos_bias"]["width"][0], length=cfg["pmos_bias"]["length"][0], fingers=cfg["pmos_bias"]["fingers"][0], multipliers=cfg["pmos_bias"]["multipliers"][0], with_dummy=cfg["pmos_bias"]["dummy_1"], with_substrate_tap=False, tie_layers=cfg["pmos_bias"]["tie_layers1"], sd_rmult=cfg["pmos_bias"]["sd_rmult"], **pmos_kwargs)
+ bias_ref = TOP << bias; bias_ref.name = "PFET_BIAS"; bias_ref.movex(X_TRUNK - bias_ref.center[0]).movey(0)
+
+ pmos_pair_ref = TOP << PMOS_PAIR; pmos_pair_ref.movex(X_TRUNK - pmos_pair_ref.center[0]).movey(bias_ref.ymin - bias_gap - (pmos_pair_ref.ymax - pmos_pair_ref.center[1]))
+ nmos_pair_ref = TOP << NMOS_PAIR; nmos_pair_ref.movex(X_TRUNK - nmos_pair_ref.center[0]).movey(pmos_pair_ref.ymin - row_gap - (nmos_pair_ref.ymax - nmos_pair_ref.center[1]))
+ mirror_ref = TOP << NMOS_MIRROR; mirror_ref.movex(X_TRUNK - mirror_ref.center[0]).movey(nmos_pair_ref.ymin - row_gap - (mirror_ref.ymax - mirror_ref.center[1]))
+ diode_ref = TOP << PMOS_DIODE; diode_ref.movex(X_TRUNK - diode_ref.center[0]).movey(mirror_ref.ymin - row_gap - (diode_ref.ymax - diode_ref.center[1]))
+
+ flat_top = TOP.flatten(); pad = pdk.get_grule("nwell", "active_diff")["min_enclosure"]
+ outer = safe_tapring(pdk, flat_top, padding=pad + outer_keepout); outer_ref = TOP << outer; center_ref_to_obj(outer_ref, flat_top)
+
+
+ # ===================================================================================
+ # MAIN VIA PLACEMENT AND ROUTING
+ # ===================================================================================
+ viam2m3 = via_stack(pdk, "met2", "met3", centered=True)
+ X_IN_P, X_IN_N = X_TRUNK - trunk_pitch_in / 2, X_TRUNK + trunk_pitch_in / 2
+ X_OUT_P, X_OUT_N = X_TRUNK - trunk_pitch_out / 2, X_TRUNK + trunk_pitch_out / 2
+
+ pfet1_src = pmos_pair_ref.ports["M1_source_E"]; pfet2_src = pmos_pair_ref.ports["M2_source_W"]; pfet1_drn = pmos_pair_ref.ports["M1_drain_E"]; pfet2_drn = pmos_pair_ref.ports["M2_drain_W"]
+ dfet1_drn = diode_ref.ports["M1_drain_E"]; dfet2_drn = diode_ref.ports["M2_drain_W"]
+
+ # PMOS bias
+ TOP << straight_route(pdk, pfet1_drn, pfet2_drn)
+ via_common_drn = TOP << viam2m3; via_common_drn.move((X_TRUNK, pfet1_drn.center[1]))
+ y_bias_src = bias_ref.ports["source_E"].center[1]; via_bias_src = TOP << viam2m3; via_bias_src.move((X_TRUNK, y_bias_src))
+ TOP << straight_route(pdk, via_common_drn.ports["top_met_N"], via_bias_src.ports["bottom_met_S"])
+
+ bias_gate = bias_ref.ports["gate_W"]; via_bias_gate_array = TOP << via_array(pdk, "met2", "met3", size=(3, 1))
+ via_bias_gate_array.move((bias_gate.center[0] - bias_gate_route_dx, bias_gate.center[1]))
+ TOP << straight_route(pdk, bias_gate, via_bias_gate_array.ports["array_row0_col2_bottom_met_E"])
+
+ # PMOS PAIR
+ via_pmos_src_L = TOP << viam2m3; via_pmos_src_L.move((X_OUT_P, pfet1_src.center[1]))
+ TOP << straight_route(pdk, pfet1_src, via_pmos_src_L.ports["bottom_met_W"])
+ via_pmos_src_R = TOP << viam2m3; via_pmos_src_R.move((X_OUT_N, pfet2_src.center[1]))
+ TOP << straight_route(pdk, pfet2_src, via_pmos_src_R.ports["bottom_met_W"])
+
+ # NMOS MIRROR
+ y_mirr_gate_L = mirror_ref.ports["M1_gate_W"].center[1]; y_mirr_gate_R = mirror_ref.ports["M2_gate_W"].center[1]
+ via_mirr_gate_L = TOP << viam2m3; via_mirr_gate_L.move((X_OUT_P, y_mirr_gate_L))
+ via_mirr_gate_R = TOP << viam2m3; via_mirr_gate_R.move((X_OUT_N, y_mirr_gate_R))
+ TOP << straight_route(pdk, via_pmos_src_L.ports["top_met_N"], via_mirr_gate_L.ports["top_met_S"])
+ TOP << straight_route(pdk, via_pmos_src_R.ports["top_met_N"], via_mirr_gate_R.ports["top_met_S"])
+ TOP << straight_route(pdk, mirror_ref.ports["M1_gate_W"], via_mirr_gate_L.ports["bottom_met_W"])
+ TOP << straight_route(pdk, mirror_ref.ports["M2_gate_W"], via_mirr_gate_R.ports["bottom_met_W"])
+
+ # PMOS DIODE
+ via_diode_drn_L = TOP << viam2m3; via_diode_drn_L.move((X_IN_P, dfet1_drn.center[1]))
+ TOP << straight_route(pdk, dfet1_drn, via_diode_drn_L.ports["bottom_met_W"])
+ via_diode_drn_R = TOP << viam2m3; via_diode_drn_R.move((X_IN_N, dfet2_drn.center[1]))
+ TOP << straight_route(pdk, dfet2_drn, via_diode_drn_R.ports["bottom_met_W"])
+
+ # PMOS PAIR GATE
+ y_pmos_gate_ref = pmos_pair_ref.ports["M2_gate_W"].center[1]
+ via_pmos_gate_L = TOP << viam2m3; via_pmos_gate_L.move((X_IN_P, y_pmos_gate_ref))
+ via_pmos_gate_R = TOP << viam2m3; via_pmos_gate_R.move((X_IN_N, y_pmos_gate_ref))
+ TOP << straight_route(pdk, via_pmos_gate_L.ports["top_met_N"], via_diode_drn_L.ports["top_met_S"])
+ TOP << straight_route(pdk, via_pmos_gate_R.ports["top_met_N"], via_diode_drn_R.ports["top_met_S"])
+ TOP << straight_route(pdk, pmos_pair_ref.ports["M2_gate_W"], via_pmos_gate_R.ports["bottom_met_W"])
+
+ via_to_gate_PMOS_PAIR_L = TOP << viam2m3; via_to_gate_PMOS_PAIR_L.move((X_IN_P, pmos_pair_ref.ports["M1_gate_W"].center[1]))
+ TOP << straight_route(pdk, pmos_pair_ref.ports["M1_gate_W"], via_to_gate_PMOS_PAIR_L.ports["bottom_met_E"])
+
+ # NMOS PAIR
+ nmos1_src = nmos_pair_ref.ports["M1_source_E"]; nmos2_src = nmos_pair_ref.ports["M2_source_W"]
+ x_via_src_L = via_pmos_src_L.center[0]; x_via_src_R = via_pmos_src_R.center[0]
+ via_nmos_src_L = TOP << viam2m3; via_nmos_src_L.move((x_via_src_L, nmos1_src.center[1]))
+ via_nmos_src_R = TOP << viam2m3; via_nmos_src_R.move((x_via_src_R, nmos2_src.center[1]))
+ TOP << straight_route(pdk, nmos1_src, via_nmos_src_L.ports["bottom_met_W"])
+ TOP << straight_route(pdk, nmos2_src, via_nmos_src_R.ports["bottom_met_W"])
+
+ nmos1_gate = nmos_pair_ref.ports["M1_gate_W"]; nmos2_gate = nmos_pair_ref.ports["M2_gate_W"]
+ x_via_gate_L = via_pmos_gate_L.center[0]; x_via_gate_R = via_pmos_gate_R.center[0]
+ via_nmos_gate_L = TOP << viam2m3; via_nmos_gate_L.move((x_via_gate_L, nmos1_gate.center[1]))
+ via_nmos_gate_R = TOP << viam2m3; via_nmos_gate_R.move((x_via_gate_R, nmos2_gate.center[1]))
+ TOP << straight_route(pdk, nmos1_gate, via_nmos_gate_L.ports["bottom_met_W"])
+ TOP << straight_route(pdk, nmos2_gate, via_nmos_gate_R.ports["bottom_met_W"])
+
+ # still NMOS pair, but also the via to input
+ y_gap_center = (nmos_pair_ref.ymin + mirror_ref.ymax) / 2; track_sep = 2.0
+ y_in_plus = y_gap_center + track_sep/2; y_in_minus = y_gap_center - track_sep/2
+ x_in_L = via_nmos_gate_L.center[0]; x_in_R = via_nmos_gate_R.center[0]
+ via_in_plus_start = TOP << viam2m3; via_in_plus_start.move((x_in_L, y_in_plus))
+ via_in_minus_start = TOP << viam2m3; via_in_minus_start.move((x_in_R, y_in_minus))
+ via_in_plus_end = TOP << viam2m3; via_in_plus_end.move((x_in_L - vin_dx, y_in_plus))
+ via_in_minus_end = TOP << viam2m3; via_in_minus_end.move((x_in_R - vin_dx, y_in_minus))
+ TOP << straight_route(pdk, via_in_plus_start.ports["bottom_met_W"], via_in_plus_end.ports["bottom_met_E"])
+ TOP << straight_route(pdk, via_in_minus_start.ports["bottom_met_W"], via_in_minus_end.ports["bottom_met_E"])
+
+ # still PMOS PAIR, but also the via to output
+ y_gap_out = (pmos_pair_ref.ymin + nmos_pair_ref.ymax) / 2; y_out_plus = y_gap_out + track_sep/2; y_out_minus = y_gap_out - track_sep/2
+ x_out_L = via_pmos_src_L.center[0]; x_out_R = via_pmos_src_R.center[0]
+ via_out_plus = TOP << viam2m3; via_out_plus.move((x_out_L, y_out_plus))
+ via_out_minus = TOP << viam2m3; via_out_minus.move((x_out_R, y_out_minus))
+ via_out_plus_end = TOP << viam2m3; via_out_plus_end.move((x_out_L + vout_dx, y_out_plus))
+ via_out_minus_end = TOP << viam2m3; via_out_minus_end.move((x_out_R + vout_dx, y_out_minus))
+ TOP << straight_route(pdk, via_out_plus.ports["bottom_met_E"], via_out_plus_end.ports["bottom_met_W"])
+ TOP << straight_route(pdk, via_out_minus.ports["bottom_met_E"], via_out_minus_end.ports["bottom_met_W"])
+
+ # NMOS PAIR C routed to NMOS MIRROR
+ TOP << c_route(pdk, nmos_pair_ref.ports["M1_drain_W"], mirror_ref.ports["M1_source_W"], extension=c_route_extension)
+ TOP << c_route(pdk, nmos_pair_ref.ports["M2_drain_E"], mirror_ref.ports["M2_source_E"], extension=c_route_extension)
+
+ # PMOS DIODE SOURCE AND THE VCM ROUTING
+ d_src_L = diode_ref.ports["M1_source_E"]; d_src_R = diode_ref.ports["M2_source_W"]; d_gate_L = diode_ref.ports["M1_gate_E"]; d_gate_R = diode_ref.ports["M2_gate_W"]
+ TOP << straight_route(pdk, d_src_L, d_src_R)
+ via_x_center = (d_src_L.center[0] + d_src_R.center[0]) / 2; y_src_level = d_src_L.center[1]; y_gate_level = d_gate_R.center[1]; y_mid_level = (mirror_ref.ymin + diode_ref.ymax) / 2
+ via_1_src = TOP << viam2m3; via_1_src.move((via_x_center, y_src_level)); via_2_gate = TOP << viam2m3; via_2_gate.move((via_x_center, y_gate_level))
+ via_3_mid = TOP << viam2m3; via_3_mid.move((via_x_center, y_mid_level))
+ TOP << straight_route(pdk, via_2_gate.ports["bottom_layer_W"], d_gate_L)
+ TOP << straight_route(pdk, via_2_gate.ports["bottom_layer_E"], d_gate_R)
+ TOP << straight_route(pdk, via_3_mid.ports["top_met_S"], via_2_gate.ports["top_met_N"]); TOP << straight_route(pdk, via_2_gate.ports["top_met_S"], via_1_src.ports["top_met_N"])
+ vcm_via = TOP << via_array(pdk, "met2", "met3", size=(2, 1)); vcm_via.move((via_x_center + vcm_dx, y_mid_level))
+ if vcm_dx > 0: TOP << straight_route(pdk, via_3_mid.ports["bottom_met_E"], vcm_via.ports["array_row0_col0_bottom_met_W"])
+ else: TOP << straight_route(pdk, via_3_mid.ports["bottom_met_W"], vcm_via.ports["array_row0_col1_bottom_met_E"])
+
+ # ===================================================================================
+ # AUTOMATIC PORTS & LABELS ADDITION
+ # ===================================================================================
+
+ # 1. VARIABLE MAPPING (User manual vars -> Auto vars)
+ via_input_P = via_in_plus_end
+ via_input_N = via_in_minus_end
+ via_output_P = via_out_plus_end
+ via_output_N = via_out_minus_end
+
+ VCM_via = vcm_via
+ Vbias_via = via_bias_gate_array
+
+ # 2. ADD PORTS
+ ALL_TOP.add_ports(via_input_P.get_ports_list(), prefix="INP_")
+ ALL_TOP.add_ports(via_input_N.get_ports_list(), prefix="INN_")
+ ALL_TOP.add_ports(via_output_P.get_ports_list(), prefix="OUTP_")
+ ALL_TOP.add_ports(via_output_N.get_ports_list(), prefix="OUTN_")
+
+ ALL_TOP.add_ports(VCM_via.get_ports_list(), prefix="VCM_")
+ ALL_TOP.add_ports(Vbias_via.get_ports_list(), prefix="VBIAS_")
+
+ ALL_TOP=rename_ports_by_orientation(ALL_TOP)
+ ALL_TOP=component_snap_to_grid(ALL_TOP)
+
+
+ ALL_TOP.info["stages"] = {
+ "PMOS_PAIR": PMOS_PAIR.info["fets"],
+ "NMOS_PAIR_INNER": NMOS_PAIR_INNER.info["fets"],
+ "NMOS_MIRROR": NMOS_MIRROR.info["fets"],
+ "PMOS_DIODE": PMOS_DIODE.info["fets"],
+ "BIAS": bias.info,
+ "bias_ref": bias_ref,
+ "via_tail": via_common_drn
+ }
+
+ return ALL_TOP
+
+if __name__ == "__main__":
+ comp = generate_ina(gf180)
+
+ # comp.pprint_ports()
+
+ comp = add_amplifier_labels(comp, gf180)
+
+ comp.name = "instrumentation_amplifier"
+
+ comp.write_gds('out_INA.gds')
+
+ comp.show()
+
+ print("...Running DRC...")
+
+ drc_result = gf180.drc_magic(comp, "instrumentation_amplifier")
+
+ drc_result = gf180.drc(comp)
+
diff --git a/src/glayout/cells/composite/instrumentation_amplifier/instrumentation_amplifier_tutorial.ipynb b/src/glayout/cells/composite/instrumentation_amplifier/instrumentation_amplifier_tutorial.ipynb
new file mode 100644
index 00000000..f6ecce4d
--- /dev/null
+++ b/src/glayout/cells/composite/instrumentation_amplifier/instrumentation_amplifier_tutorial.ipynb
@@ -0,0 +1,431 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "434c6fbf-1c23-4312-a046-a8c98ee89845",
+ "metadata": {},
+ "source": [
+ "# Amplifier Generation Tutorial \n",
+ "\n",
+ "**By gLayout Team**\n",
+ "\n",
+ "__Content creators:__ Aleta"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f7a625c4-3af3-476c-b7d4-51de6e2e2fa2",
+ "metadata": {},
+ "source": [
+ "___\n",
+ "# Tutorial Objectives\n",
+ "\n",
+ "This notebook is a tutorial on-\n",
+ "* Generation of the instrumentation amplifier\n",
+ "* How to manually generate VDD and VSS routing for the instrumentation amplifier\n",
+ "* How to generate ports for VDD, VSS, VBIAS, and VCM"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6881c711-71db-46b4-82db-ee7a52bcadbe",
+ "metadata": {},
+ "source": [
+ "## Creating the Environment for Layout Generation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "ece0156c-11ab-460b-9283-769d68e8b108",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import gdstk\n",
+ "import svgutils.transform as sg\n",
+ "import IPython.display\n",
+ "import ipywidgets as widgets\n",
+ "from gdsfactory import Component\n",
+ "from gdsfactory.port import Port\n",
+ "from gdsfactory.components import rectangle\n",
+ "from glayout import gf180\n",
+ "from glayout import nmos, pmos, tapring\n",
+ "from glayout.util.comp_utils import evaluate_bbox, prec_center, align_comp_to_port\n",
+ "from glayout import rename_ports_by_orientation\n",
+ "# ROUTING\n",
+ "from glayout.routing.straight_route import straight_route\n",
+ "from glayout.routing.c_route import c_route\n",
+ "\n",
+ "# VIAS import\n",
+ "from glayout import via_stack, via_array\n",
+ "\n",
+ "hide = widgets.Output()\n",
+ "\n",
+ "# display helpers\n",
+ "def display_gds(gds_file, path, scale=3):\n",
+ " top_level_cell = gdstk.read_gds(gds_file).top_level()[0]\n",
+ " top_level_cell.write_svg(os.path.join(path, \"out.svg\"))\n",
+ " fig = sg.fromfile(os.path.join(path, \"out.svg\"))\n",
+ " fig.set_size((str(float(fig.width) * scale), str(float(fig.height) * scale)))\n",
+ " fig.save(os.path.join(path, \"out.svg\"))\n",
+ " IPython.display.display(IPython.display.SVG(os.path.join(path, \"out.svg\")))\n",
+ "def display_component(component, path, scale=3):\n",
+ " with hide:\n",
+ " component.write_gds(os.path.join(path, \"out.gds\"))\n",
+ " display_gds(os.path.join(path, \"out.gds\"), path, scale)\n",
+ " \n",
+ "# Dummy wrapper if component_snap_to_grid is not directly available in your version (*)\n",
+ "def component_snap_to_grid(component):\n",
+ " return component\n",
+ "\n",
+ "from instrumentation_amplifier import generate_ina, add_amplifier_labels"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dcd4179b-5955-4232-9bbb-922f3561dc72",
+ "metadata": {},
+ "source": [
+ "## Define the pmos and nmos parameter "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "bf3163d9-a81e-4b1e-86f9-c1e2b7161435",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# default kwargs (gLayout parameter for 1 NMOS/PMOS)\n",
+ "nmos_kwargs = {\n",
+ " \"with_tie\": True, \"with_dnwell\": False, \"sd_route_topmet\": \"met2\",\n",
+ " \"gate_route_topmet\": \"met2\", \"sd_route_left\": True, \"rmult\": None,\n",
+ " \"gate_rmult\": 1, \"interfinger_rmult\": 1, \"substrate_tap_layers\": (\"met2\", \"met1\"),\n",
+ " \"dummy_routes\": True\n",
+ "}\n",
+ "\n",
+ "pmos_kwargs = {\n",
+ " \"with_tie\": True, \"dnwell\": False, \"sd_route_topmet\": \"met2\",\n",
+ " \"gate_route_topmet\": \"met2\", \"sd_route_left\": True, \"rmult\": None,\n",
+ " \"gate_rmult\": 1, \"interfinger_rmult\": 1, \"substrate_tap_layers\": (\"met2\", \"met1\"),\n",
+ " \"dummy_routes\": True\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fb2b4b72-0512-44f2-b372-cee0e1039a7d",
+ "metadata": {},
+ "source": [
+ "## Define the instrumentation amplifier parameters\n",
+ "\n",
+ "Configuration : Contains the PMOS and NMOS device parameters used in the amplifier, including transistor width, length,\n",
+ " finger count, multipliers, dummy devices, and metal tie layers, which define the sizing and layout\n",
+ " configuration of each transistor block.\n",
+ " \n",
+ "x_distance : Sets the horizontal separation between the left and right sides of the differential pair.\n",
+ "\n",
+ "row_gap : Defines the vertical spacing between different transistor rows.\n",
+ "\n",
+ "bias_gap : Defines the vertical spacing between the bias block and the main circuit.\n",
+ "\n",
+ "trunk_pitch_in: Controls the pitch (spacing/width) of the vertical routing trunks for the input signals.\n",
+ "\n",
+ "trunk_pitch_out: Controls the pitch of the vertical routing trunks for the output signals.\n",
+ "\n",
+ "outpad_margin : Sets the spacing buffer between the core amplifier layout and the I/O pads or chip boundary.\n",
+ "\n",
+ "outer_keepout : Defines the clearance distance for the outer guard ring.\n",
+ "\n",
+ "nmos_pair_outer_ring_padding: Sets the spacing between the NMOS differential pair and the outer guard ring.\n",
+ "\n",
+ "bias_gate_route_dx: Specifies the horizontal extension used to route the VBIAS connection.\n",
+ "\n",
+ "c_route_extension: Defines the extension length for the common routing path.\n",
+ "\n",
+ "vcm_dx : Sets the horizontal offset for the VCM (common-mode voltage) connection.\n",
+ "\n",
+ "vin_dx : Specifies the horizontal routing extension for the input connections.\n",
+ "\n",
+ "vout_dx : Specifies the horizontal routing extension for the output connections."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "e1773b4d-90b5-4c36-85f7-170ca3cb879a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cfg= {\n",
+ " \"pmos_pair\": {\"pdk\": gf180, \"placement\": \"horizontal\", \"width\": (6, 6), \"length\": (3, 3), \"fingers\": (6, 6), \"multipliers\": (1, 1), \"dummy_1\": (True, True), \"dummy_2\": (True, True), \"tie_layers1\": (\"met2\", \"met1\"), \"tie_layers2\": (\"met2\", \"met1\"), \"sd_rmult\": 1},\n",
+ " \"pmos_bias\": {\"pdk\": gf180, \"placement\": \"horizontal\", \"width\": (0.5, 0.5), \"length\": (6.9, 6.9), \"fingers\": (1, 1), \"multipliers\": (1, 1), \"dummy_1\": (True, True), \"dummy_2\": (True, True), \"tie_layers1\": (\"met2\", \"met1\"), \"tie_layers2\": (\"met2\", \"met1\"), \"sd_rmult\": 1},\n",
+ " \"nmos_pair\": {\"pdk\": gf180, \"placement\": \"horizontal\", \"width\": (3, 3), \"length\": (3, 3), \"fingers\": (1, 1), \"multipliers\": (1, 1), \"dummy_1\": (True, True), \"dummy_2\": (True, True), \"tie_layers1\": (\"met2\", \"met1\"), \"tie_layers2\": (\"met2\", \"met1\"), \"sd_rmult\": 1},\n",
+ " \"nmos_mirror\": {\"pdk\": gf180, \"placement\": \"horizontal\", \"width\": (0.5, 0.5), \"length\": (6.9, 6.9), \"fingers\": (1, 1), \"multipliers\": (1, 1), \"dummy_1\": (True, True), \"dummy_2\": (True, True), \"tie_layers1\": (\"met2\", \"met1\"), \"tie_layers2\": (\"met2\", \"met1\"), \"sd_rmult\": 1},\n",
+ " \"pmos_diode\": {\"pdk\": gf180, \"placement\": \"horizontal\", \"width\": (0.5, 0.5), \"length\": (6.9, 6.9), \"fingers\": (1, 1), \"multipliers\": (1, 1), \"dummy_1\": (True, True), \"dummy_2\": (True, True), \"tie_layers1\": (\"met2\", \"met1\"), \"tie_layers2\": (\"met2\", \"met1\"), \"sd_rmult\": 1}\n",
+ " }"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "181d4071-2b28-4bc1-9bc4-dd065945e003",
+ "metadata": {},
+ "source": [
+ "## Generate GDS "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ad9fcd7a-561a-4fe0-887f-6a651b88781d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ALL_TOP=generate_ina( \n",
+ " pdk=gf180,\n",
+ " CFG=cfg, # pdk\n",
+ " x_distance=5, # Sets the horizontal separation between the left and right sides of the differential pairs.\n",
+ " row_gap=6.0, # Defines the vertical spacing between the different transistor rows\n",
+ " bias_gap=4.0, # Defines the vertical spacing between the bias block.\n",
+ " trunk_pitch_in=3.4, # Controls the width/pitch of the vertical signal trunks for input.\n",
+ " trunk_pitch_out=1.0, # Controls the width/pitch of the vertical signal trunks for output.\n",
+ " outpad_margin=35.0, # Define the spacing buffer between the core amplifier circuit and the external I/O pads (or the chip boundary).\n",
+ " outer_keepout=6.0, # Sets the clearance distance for the outermost guard ring.\n",
+ " nmos_pair_outer_ring_padding=2.2, # outer ring padring spacing for NMOS pair\n",
+ " bias_gate_route_dx=25.0, # Specifies the horizontal extension length for the VBIAS via\n",
+ " c_route_extension=6.0, # Set the c_route length and structure.\n",
+ " vcm_dx=-20.0, # Sets the offset location for the Common Mode Voltage (VCM) connection (the via)\n",
+ " vin_dx=40.0, # Specifies the horizontal extension length for the input (the via)\n",
+ " vout_dx=40.0, # Specifies the horizontal extension length for the output (the via)\n",
+ " nmos_kwargs=nmos_kwargs,\n",
+ " pmos_kwargs=pmos_kwargs\n",
+ " )\n",
+ "ALL_TOP = add_amplifier_labels(ALL_TOP, gf180)\n",
+ "ALL_TOP.name = \"INA_PARAMETERIZED\"\n",
+ "ALL_TOP.write_gds(\"INA_PARAMETERIZED.gds\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d8d17a58-725d-4483-b4af-a15594a3fbb7",
+ "metadata": {},
+ "source": [
+ "## Get access to bias_ref and vcm"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "d75d7ea2-8466-4bed-9e81-dc58f0901d16",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "viaarray = via_array(gf180, \"met2\", \"met3\", (2,1))\n",
+ "bias_ref = ALL_TOP.info[\"stages\"][\"bias_ref\"]\n",
+ "via_pmos_src_L = ALL_TOP.info[\"stages\"][\"via_tail\"]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0c0ca8a9-e80c-41f6-a880-3f3198ca44a0",
+ "metadata": {},
+ "source": [
+ "## Route VDD and VSS"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "536f3b96-8092-4ab2-a3c0-56b832104928",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pdk=gf180\n",
+ "# VDD LEFT SIDE WAY\n",
+ "VDD1_a_via = ALL_TOP << viaarray\n",
+ "VDD1_a_via.move(bias_ref.ports[\"tie_N_array_row0_col0_top_met_E\"].center)\n",
+ "VDD2_via = ALL_TOP << viaarray\n",
+ "VDD2_via.move(bias_ref.ports[\"tie_S_array_row0_col0_top_met_E\"].center)\n",
+ "\n",
+ "\n",
+ "# VIA ARRAY TO SOURCE BIAS REF\n",
+ "bias_src_port_1 = bias_ref.ports[\"multiplier_0_drain_W\"]\n",
+ "via_ref_vdd_bias = VDD1_a_via.center\n",
+ "VDD1_b_via = ALL_TOP << viaarray\n",
+ "VDD1_b_via.move((via_ref_vdd_bias[0], bias_src_port_1.center[1]))\n",
+ "#ALL_TOP << straight_route(pdk,VDD1_b_via.ports[\"array_row0_col0_bottom_layer_W\"], bias_src_port_1, width = 0.8)\n",
+ "VDD1_c_via = ALL_TOP << viaarray\n",
+ "VDD1_c_via.move((12, bias_src_port_1.center[1]))\n",
+ "ALL_TOP << straight_route(pdk,VDD1_b_via.ports[\"array_row0_col0_bottom_layer_W\"], VDD1_c_via.ports[\"array_row0_col0_bottom_layer_E\"])\n",
+ "\n",
+ "# VDD RIGHT SIDE WAY **\n",
+ "VDD5_via = ALL_TOP << viaarray\n",
+ "VDD6_via = ALL_TOP << viaarray\n",
+ "VDD5_via.movex(VDD1_c_via.center[0]).movey(2.3)\n",
+ "VDD6_via.movex(VDD1_c_via.center[0]).movey(-2.3)\n",
+ "\n",
+ "# ---- ROUTE VDD\n",
+ "route_vertical_VDD_L = straight_route(pdk,VDD1_a_via.ports[\"top_met_N\"],VDD2_via.ports[\"top_met_S\"], width = 2)\n",
+ "ALL_TOP << route_vertical_VDD_L\n",
+ "route_vertical_VDD_R = straight_route(pdk,VDD5_via.ports[\"top_met_N\"],VDD6_via.ports[\"top_met_S\"], width = 2)\n",
+ "ALL_TOP << route_vertical_VDD_R\n",
+ "\n",
+ "# VSS LEFT SIDE WAY\n",
+ "VSS1_via = ALL_TOP << viaarray\n",
+ "VSS1_via.movex(VDD1_a_via.center[0]).movey(9.3)\n",
+ "VSS1_via.movex(6)\n",
+ "VSS2_via = ALL_TOP << viaarray\n",
+ "VSS3_via = ALL_TOP << viaarray\n",
+ "VSS4_via = ALL_TOP << viaarray\n",
+ "VSS2_via.movex(VSS1_via.center[0]).movey(-7)\n",
+ "VSS3_via.movex(VSS1_via.center[0]).movey(-26)\n",
+ "VSS4_via.movex(VSS1_via.center[0]).movey(-45.2)\n",
+ "VSS5_via = ALL_TOP << viaarray\n",
+ "VSS6_via = ALL_TOP << viaarray\n",
+ "VSS7_via = ALL_TOP << viaarray\n",
+ "VSS5_via.movex(VSS1_via.center[0]).movey(-46.5)\n",
+ "VSS6_via.movex(VSS1_via.center[0]).movey(-58.7)\n",
+ "VSS7_via.movex(VSS1_via.center[0]).movey(-72.6)\n",
+ "\n",
+ "# ---- ROUTE VSS LEFT\n",
+ "route_vertical_VSS_L = straight_route(pdk,VSS1_via.ports[\"top_met_N\"],VSS7_via.ports[\"top_met_S\"], width = 2)\n",
+ "ALL_TOP << route_vertical_VSS_L\n",
+ "\n",
+ "# VSS RIGHT SIDE WAY\n",
+ "VSS8_via = ALL_TOP << viaarray\n",
+ "VSS9_via = ALL_TOP << viaarray\n",
+ "VSS10_via = ALL_TOP << viaarray\n",
+ "VSS11_via = ALL_TOP << viaarray\n",
+ "VSS12_via = ALL_TOP << viaarray\n",
+ "VSS13_via = ALL_TOP << viaarray\n",
+ "VSS14_via = ALL_TOP << viaarray\n",
+ "VSS8_via.movex(VDD1_a_via.center[0]).movey(9.3)\n",
+ "VSS8_via.movex(18)\n",
+ "VSS9_via.movex(VSS8_via.center[0]).movey(-7)\n",
+ "VSS10_via.movex(VSS8_via.center[0]).movey(-26)\n",
+ "VSS11_via.movex(VSS8_via.center[0]).movey(-45.2)\n",
+ "VSS12_via.movex(VSS8_via.center[0]).movey(-46.5)\n",
+ "VSS13_via.movex(VSS8_via.center[0]).movey(-58.7)\n",
+ "VSS14_via.movex(VSS8_via.center[0]).movey(-72.6)\n",
+ "\n",
+ "# ---- ROUTE VSS RIGHT\n",
+ "route_vertical_VSS_R = straight_route(pdk,VSS8_via.ports[\"top_met_N\"],VSS14_via.ports[\"top_met_S\"], width = 2)\n",
+ "ALL_TOP << route_vertical_VSS_R"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fccce6bd-23a4-4ff8-a419-5820ec232a97",
+ "metadata": {},
+ "source": [
+ "## Add ports & label for VDD, VBIAS, VCM, and VSS"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "accc84ba-f2b0-4dbc-8ca2-9e90f15c97b9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ALL_TOP.add_ports(VDD1_a_via.get_ports_list(), prefix=\"VDD_TOPL_\")\n",
+ "ALL_TOP.add_ports(VDD5_via.get_ports_list(), prefix=\"VDD_TOPR_\")\n",
+ "ALL_TOP.add_ports(VDD2_via.get_ports_list(), prefix=\"VDD_BOTTOML_\")\n",
+ "ALL_TOP.add_ports(VDD6_via.get_ports_list(), prefix=\"VDD_BOTTOMR_\")\n",
+ "\n",
+ "ALL_TOP.add_ports(VSS1_via.get_ports_list(), prefix=\"VSS_TOPL_\")\n",
+ "ALL_TOP.add_ports(VSS7_via.get_ports_list(), prefix=\"VSS_BOTTOML_\")\n",
+ "ALL_TOP.add_ports(VSS8_via.get_ports_list(), prefix=\"VSS_TOPR_\")\n",
+ "ALL_TOP.add_ports(VSS14_via.get_ports_list(), prefix=\"VSS_BOTTOMR_\")\n",
+ "\n",
+ "ALL_TOP.add_ports(via_pmos_src_L.get_ports_list(), prefix=\"VTAIL_\")\n",
+ "\n",
+ "component = component_snap_to_grid(rename_ports_by_orientation(ALL_TOP))\n",
+ "##########################################################################################################\n",
+ "# LABELLING\n",
+ "##########################################################################################################\n",
+ "# VSS \n",
+ "psize=(0.5,0.5)\n",
+ "move_info = list()\n",
+ "vsslabel = rectangle(layer=pdk.get_glayer(\"met3_pin\"),size=psize,centered=True).copy()\n",
+ "vsslabel.add_label(text=\"VSS\",layer=pdk.get_glayer(\"met3_label\"))\n",
+ "move_info.append((vsslabel,component.ports[\"VSS_TOPL_top_met_N\"],None))\n",
+ "move_info.append((vsslabel,component.ports[\"VSS_BOTTOML_top_met_N\"],None))\n",
+ "move_info.append((vsslabel,component.ports[\"VSS_TOPR_top_met_N\"],None))\n",
+ "move_info.append((vsslabel,component.ports[\"VSS_BOTTOMR_top_met_N\"],None))\n",
+ "#vss_ref= ALL_TOP << vsslabel\n",
+ "\n",
+ "#VDD\n",
+ "vddlabel = rectangle(layer=pdk.get_glayer(\"met3_pin\"),size=psize,centered=True).copy()\n",
+ "vddlabel.add_label(text=\"VDD\",layer=pdk.get_glayer(\"met3_pin\"))\n",
+ "move_info.append((vddlabel,component.ports[\"VDD_TOPL_top_met_N\"],None))\n",
+ "move_info.append((vddlabel,component.ports[\"VDD_TOPR_top_met_N\"],None))\n",
+ "move_info.append((vddlabel,component.ports[\"VDD_BOTTOML_top_met_S\"],None))\n",
+ "move_info.append((vddlabel,component.ports[\"VDD_BOTTOMR_top_met_S\"],None))\n",
+ "#vdd_ref = ALL_TOP << vddlabel\n",
+ "\n",
+ "# Vbias label\n",
+ "vbiaslabel = rectangle(layer=pdk.get_glayer(\"met2_pin\"),size=psize,centered=True).copy()\n",
+ "vbiaslabel.add_label(text=\"VBIAS\",layer=pdk.get_glayer(\"met2_pin\"))\n",
+ "vbiaslabel_ref = align_comp_to_port(vbiaslabel, component.ports[\"VBIAS_top_met_W\"], alignment=('c','c'))\n",
+ "vbiaslabel_ref.movex(0.4)\n",
+ "ALL_TOP.add(vbiaslabel_ref)\n",
+ "#vbias_ref = ALL_TOP << vbiaslabel\n",
+ "\n",
+ "# v_node label\n",
+ "vtaillabel= rectangle(layer=pdk.get_glayer(\"met3_pin\"),size=psize,centered=True).copy()\n",
+ "vtaillabel.add_label(text=\"VTAIL\",layer=pdk.get_glayer(\"met3_pin\"))\n",
+ "vtaillabel_ref = align_comp_to_port(vtaillabel, component.ports[\"VTAIL_top_met_W\"], alignment=('c','c'))\n",
+ "vtaillabel_ref.movex(0.4)\n",
+ "ALL_TOP.add(vtaillabel_ref)\n",
+ "\n",
+ "# VCM label\n",
+ "vcmlabel = rectangle(layer=pdk.get_glayer(\"met2_pin\"),size=psize,centered=True).copy()\n",
+ "vcmlabel.add_label(text=\"VCM\",layer=pdk.get_glayer(\"met2_pin\"))\n",
+ "vcmlabel_ref = align_comp_to_port(vcmlabel, component.ports[\"VCM_top_met_W\"], alignment=('c','c'))\n",
+ "vcmlabel_ref.movex(0.4)\n",
+ "ALL_TOP.add(vcmlabel_ref)\n",
+ "#vcm_ref = ALL_TOP << vcmlabel\n",
+ "for comp, prt, alignment in move_info:\n",
+ " alignment = ('c','b') if alignment is None else alignment\n",
+ " compref = align_comp_to_port(comp, prt, alignment=alignment)\n",
+ " ALL_TOP.add(compref)\n",
+ "component = ALL_TOP.flatten()\n",
+ "\n",
+ "display_component(component, scale =0.5,path=\"\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9a8b8e1e-452b-437e-9eae-1cd02866ef06",
+ "metadata": {},
+ "source": [
+ "## Notes\n",
+ "\n",
+ "You may modify the instrumentation amplifier specifications according to your design requirements. The VDD and VSS routing are not generated automatically and should be created manually by following the routing and access methods demonstrated in this tutorial. Other parameters, such as device sizing and gain configuration, may also be adjusted as needed."
+ ]
+ }
+ ],
+ "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
+}
diff --git a/src/glayout/cells/elementary/transmission_gate/transmission_gate.py b/src/glayout/cells/elementary/transmission_gate/transmission_gate.py
index 7d9141df..432c6fd3 100644
--- a/src/glayout/cells/elementary/transmission_gate/transmission_gate.py
+++ b/src/glayout/cells/elementary/transmission_gate/transmission_gate.py
@@ -21,7 +21,8 @@
run_evaluation = None
def add_tg_labels(tg_in: Component,
- pdk: MappedPDK
+ pdk: MappedPDK,
+ placement: str ="mirror"
) -> Component:
tg_in.unlock()
@@ -29,36 +30,72 @@ def add_tg_labels(tg_in: Component,
# list that will contain all port/comp info
move_info = list()
# create labels and append to info list
- # vin
- vinlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
- vinlabel.add_label(text="VIN",layer=pdk.get_glayer("met2_label"))
- move_info.append((vinlabel,tg_in.ports["N_multiplier_0_source_E"],None))
-
- # vout
- voutlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
- voutlabel.add_label(text="VOUT",layer=pdk.get_glayer("met2_label"))
- move_info.append((voutlabel,tg_in.ports["P_multiplier_0_drain_W"],None))
-
- # vcc
- vcclabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.5,0.5),centered=True).copy()
- vcclabel.add_label(text="VCC",layer=pdk.get_glayer("met2_label"))
- move_info.append((vcclabel,tg_in.ports["P_tie_S_top_met_S"],None))
-
- # vss
- vsslabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.5,0.5),centered=True).copy()
- vsslabel.add_label(text="VSS",layer=pdk.get_glayer("met2_label"))
- move_info.append((vsslabel,tg_in.ports["N_tie_S_top_met_N"], None))
-
- # VGP
- vgplabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
- vgplabel.add_label(text="VGP",layer=pdk.get_glayer("met2_label"))
- move_info.append((vgplabel,tg_in.ports["P_multiplier_0_gate_E"], None))
-
- # VGN
- vgnlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
- vgnlabel.add_label(text="VGN",layer=pdk.get_glayer("met2_label"))
- move_info.append((vgnlabel,tg_in.ports["N_multiplier_0_gate_E"], None))
-
+ if placement == "mirror":
+ # vin
+ vinlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
+ vinlabel.add_label(text="VIN",layer=pdk.get_glayer("met2_label"))
+ move_info.append((vinlabel,tg_in.ports["N_multiplier_0_source_E"],None))
+
+ # vout
+ voutlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
+ voutlabel.add_label(text="VOUT",layer=pdk.get_glayer("met2_label"))
+ move_info.append((voutlabel,tg_in.ports["P_multiplier_0_drain_W"],None))
+
+ # vcc
+ vcclabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.5,0.5),centered=True).copy()
+ vcclabel.add_label(text="VCC",layer=pdk.get_glayer("met2_label"))
+ move_info.append((vcclabel,tg_in.ports["P_tie_S_top_met_S"],None))
+
+ # vss
+ vsslabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.5,0.5),centered=True).copy()
+ vsslabel.add_label(text="VSS",layer=pdk.get_glayer("met2_label"))
+ move_info.append((vsslabel,tg_in.ports["N_tie_S_top_met_N"], None))
+
+ # VGP
+ vgplabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
+ vgplabel.add_label(text="VGP",layer=pdk.get_glayer("met2_label"))
+ move_info.append((vgplabel,tg_in.ports["P_multiplier_0_gate_E"], None))
+
+ # VGN
+ vgnlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
+ vgnlabel.add_label(text="VGN",layer=pdk.get_glayer("met2_label"))
+ move_info.append((vgnlabel,tg_in.ports["N_multiplier_0_gate_E"], None))
+
+ elif placement in ["vertical", "vertical_invert", "horizontal"]:
+ # VSS
+ vsslabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.5,0.5),centered=True).copy()
+ vsslabel.add_label(text="VSS",layer=pdk.get_glayer("met2_label"))
+ move_info.append((vsslabel,tg_in.ports["N_tie_N_top_met_W"],None))
+ #gnd_ref = top_level << gndlabel;
+
+ #suply
+ vcclabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.5,0.5),centered=True).copy()
+ vcclabel.add_label(text="VCC",layer=pdk.get_glayer("met2_label"))
+ move_info.append((vcclabel,tg_in.ports["P_tie_N_top_met_W"],None))
+ #sup_ref = top_level << suplabel;
+
+ # output
+ voutlabel = rectangle(layer=pdk.get_glayer("met3_pin"),size=(0.27,0.27),centered=True).copy()
+ voutlabel.add_label(text="VOUT",layer=pdk.get_glayer("met3_label"))
+ move_info.append((voutlabel,tg_in.ports["P_drain_top_met_N"],None))
+ #op_ref = top_level << outputlabel;
+
+ # input
+ vinlabel = rectangle(layer=pdk.get_glayer("met3_pin"),size=(0.27,0.27),centered=True).copy()
+ vinlabel.add_label(text="VIN",layer=pdk.get_glayer("met3_label"))
+ move_info.append((vinlabel,tg_in.ports["P_source_top_met_N"], None))
+ #ip_ref = top_level << inputlabel;
+
+ # VGN
+ vgnlabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
+ vgnlabel.add_label(text="VGN",layer=pdk.get_glayer("met2_label"))
+ move_info.append((vgnlabel,tg_in.ports["N_gate_S"], None))
+
+ # VGP
+ vgplabel = rectangle(layer=pdk.get_glayer("met2_pin"),size=(0.27,0.27),centered=True).copy()
+ vgplabel.add_label(text="VGP",layer=pdk.get_glayer("met2_label"))
+ move_info.append((vgplabel,tg_in.ports["P_gate_N"], None))
+
# move everything to position
for comp, prt, alignment in move_info:
alignment = ('c','b') if alignment is None else alignment
@@ -88,47 +125,78 @@ def get_component_netlist(component):
# Fallback: return the string representation (should not happen in normal operation)
return component.info.get('netlist', '')
-def sky130_add_tg_labels(tg_in: Component) -> Component:
+def sky130_add_tg_labels(tg_in: Component, placement: str= "mirror") -> Component:
tg_in.unlock()
# define layers`
met1_pin = (68,16)
+ met2_pin = (69,16)
met1_label = (68,5)
+ met2_label = (69,5)
li1_pin = (67,16)
li1_label = (67,5)
# list that will contain all port/comp info
move_info = list()
# create labels and append to info list
- # vin
- vinlabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
- vinlabel.add_label(text="VIN",layer=met1_label)
- move_info.append((vinlabel,tg_in.ports["N_multiplier_0_source_E"],None))
-
- # vout
- voutlabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
- voutlabel.add_label(text="VOUT",layer=met1_label)
- move_info.append((voutlabel,tg_in.ports["P_multiplier_0_drain_W"],None))
-
- # vcc
- vcclabel = rectangle(layer=met1_pin,size=(0.5,0.5),centered=True).copy()
- vcclabel.add_label(text="VCC",layer=met1_label)
- move_info.append((vcclabel,tg_in.ports["P_tie_S_top_met_S"],None))
-
- # vss
- vsslabel = rectangle(layer=met1_pin,size=(0.5,0.5),centered=True).copy()
- vsslabel.add_label(text="VSS",layer=met1_label)
- move_info.append((vsslabel,tg_in.ports["N_tie_S_top_met_N"], None))
-
- # VGP
- vgplabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
- vgplabel.add_label(text="VGP",layer=met1_label)
- move_info.append((vgplabel,tg_in.ports["P_multiplier_0_gate_E"], None))
-
- # VGN
- vgnlabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
- vgnlabel.add_label(text="VGN",layer=met1_label)
- move_info.append((vgnlabel,tg_in.ports["N_multiplier_0_gate_E"], None))
+ if placement == "mirror":
+ # vin
+ vinlabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
+ vinlabel.add_label(text="VIN",layer=met1_label)
+ move_info.append((vinlabel,tg_in.ports["N_multiplier_0_source_E"],None))
+
+ # vout
+ voutlabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
+ voutlabel.add_label(text="VOUT",layer=met1_label)
+ move_info.append((voutlabel,tg_in.ports["P_multiplier_0_drain_W"],None))
+
+ # vcc
+ vcclabel = rectangle(layer=met1_pin,size=(0.5,0.5),centered=True).copy()
+ vcclabel.add_label(text="VCC",layer=met1_label)
+ move_info.append((vcclabel,tg_in.ports["P_tie_S_top_met_S"],None))
+
+ # vss
+ vsslabel = rectangle(layer=met1_pin,size=(0.5,0.5),centered=True).copy()
+ vsslabel.add_label(text="VSS",layer=met1_label)
+ move_info.append((vsslabel,tg_in.ports["N_tie_S_top_met_N"], None))
+
+ # VGP
+ vgplabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
+ vgplabel.add_label(text="VGP",layer=met1_label)
+ move_info.append((vgplabel,tg_in.ports["P_multiplier_0_gate_E"], None))
+
+ # VGN
+ vgnlabel = rectangle(layer=met1_pin,size=(0.27,0.27),centered=True).copy()
+ vgnlabel.add_label(text="VGN",layer=met1_label)
+ move_info.append((vgnlabel,tg_in.ports["N_multiplier_0_gate_E"], None))
+
+ elif placement in ["vertical", "vertical_invert", "horizontal"]:
+ # VIN & VOUT
+ vin_label = rectangle(layer=met2_pin, size=(0.27,0.27), centered=True).copy()
+ vin_label.add_label(text="VIN", layer=met2_label)
+ move_info.append((vin_label, tg_in.ports["P_source_top_met_N"], None))
+
+ vout_label = rectangle(layer=met2_pin, size=(0.27,0.27), centered=True).copy()
+ vout_label.add_label(text="VOUT", layer=met2_label)
+ move_info.append((vout_label, tg_in.ports["P_drain_top_met_N"], None))
+
+ # VDD (VCC) & VSS
+ vcc_label = rectangle(layer=met1_pin, size=(0.5,0.5), centered=True).copy()
+ vcc_label.add_label(text="VCC", layer=met1_label)
+ move_info.append((vcc_label, tg_in.ports["P_tie_N_top_met_W"], None))
+
+ vss_label = rectangle(layer=met1_pin, size=(0.5,0.5), centered=True).copy()
+ vss_label.add_label(text="VSS", layer=met1_label)
+ move_info.append((vss_label, tg_in.ports["N_tie_N_top_met_W"], None))
+
+ # CLK & CLK_INV (Nama berubah untuk Chopper)
+ vgnlabel = rectangle(layer=met1_pin, size=(0.27,0.27), centered=True).copy()
+ vgnlabel.add_label(text="VGN", layer=met1_label)
+ move_info.append((vgnlabel, tg_in.ports["N_gate_S"], None))
+
+ vgplabel = rectangle(layer=met1_pin, size=(0.27,0.27), centered=True).copy()
+ vgplabel.add_label(text="VGP", layer=met1_label)
+ move_info.append((vgplabel, tg_in.ports["P_gate_N"], None))
# move everything to position
for comp, prt, alignment in move_info:
@@ -149,15 +217,19 @@ def tg_netlist(nfet: Component, pfet: Component) -> Netlist:
return netlist
-@cell
def transmission_gate(
pdk: MappedPDK,
+ placement: str = "mirror",
width: tuple[float,float] = (1,1),
length: tuple[float,float] = (None,None),
fingers: tuple[int,int] = (1,1),
multipliers: tuple[int,int] = (1,1),
substrate_tap: bool = False,
- tie_layers: tuple[str,str] = ("met2","met1"),
+ dummy_1: tuple[bool,bool] = (True,True),
+ dummy_2: tuple[bool,bool] = (True,True),
+ tie_layers1: tuple[str,str] = ("met2","met1"),
+ tie_layers2: tuple[str,str] = ("met2","met1"),
+ sd_rmult: int=1,
**kwargs
) -> Component:
"""
@@ -170,18 +242,72 @@ def transmission_gate(
top_level = Component(name="transmission_gate")
#two fets
- nfet = nmos(pdk, width=width[0], fingers=fingers[0], multipliers=multipliers[0], with_dummy=True, with_dnwell=False, with_substrate_tap=False, length=length[0], **kwargs)
- pfet = pmos(pdk, width=width[1], fingers=fingers[1], multipliers=multipliers[1], with_dummy=True, with_substrate_tap=False, length=length[1], **kwargs)
+ nfet = nmos(pdk, width=width[0], fingers=fingers[0], multipliers=multipliers[0], with_dummy=dummy_1, with_dnwell=False, with_substrate_tap=False, length=length[0], tie_layers=tie_layers1, sd_rmult=sd_rmult, **kwargs)
+ pfet = pmos(pdk, width=width[1], fingers=fingers[1], multipliers=multipliers[1], with_dummy=dummy_2, with_substrate_tap=False, length=length[1], tie_layers=tie_layers2, sd_rmult=sd_rmult, **kwargs)
+
nfet_ref = top_level << nfet
pfet_ref = top_level << pfet
- pfet_ref = rename_ports_by_orientation(pfet_ref.mirror_y())
-
- #Relative move
- pfet_ref.movey(nfet_ref.ymax + evaluate_bbox(pfet_ref)[1]/2 + pdk.util_max_metal_seperation())
+ viam2m3 = via_stack(pdk, "met2", "met3", centered=True)
+ ref_dimensions = evaluate_bbox(nfet)
- #Routing
- top_level << c_route(pdk, nfet_ref.ports["multiplier_0_source_E"], pfet_ref.ports["multiplier_0_source_E"])
- top_level << c_route(pdk, nfet_ref.ports["multiplier_0_drain_W"], pfet_ref.ports["multiplier_0_drain_W"], viaoffset=False)
+ if placement == "mirror":
+ pfet_ref = rename_ports_by_orientation(pfet_ref.mirror_y())
+ #Relative move
+ pfet_ref.movey(nfet_ref.ymax + evaluate_bbox(pfet_ref)[1]/2 + pdk.util_max_metal_seperation())
+
+ #Routing
+ top_level << c_route(pdk, nfet_ref.ports["multiplier_0_source_E"], pfet_ref.ports["multiplier_0_source_E"])
+ top_level << c_route(pdk, nfet_ref.ports["multiplier_0_drain_W"], pfet_ref.ports["multiplier_0_drain_W"], viaoffset=False)
+
+ elif placement == "vertical":
+ nfet_ref.mirror_y()
+ nfet_ref.movey(pfet_ref.ymin - ref_dimensions[1]/2 - pdk.util_max_metal_seperation()-0.5)
+
+ #via for input and output
+ drain_P_via = top_level << viam2m3
+ source_P_via = top_level << viam2m3
+
+ drain_P_via.move(pfet_ref.ports["multiplier_0_source_W"].center).movex(-0.75).movey((nfet_ref.ymin-nfet_ref.ymax)/1.7)
+ source_P_via.move(pfet_ref.ports["multiplier_0_drain_E"].center).movex(0.75).movey((nfet_ref.ymin-nfet_ref.ymax)/1.7)
+ top_level << c_route(pdk, pfet_ref.ports["multiplier_0_source_W"], nfet_ref.ports["multiplier_0_source_W"])
+ top_level << c_route(pdk, pfet_ref.ports["multiplier_0_drain_E"], nfet_ref.ports["multiplier_0_drain_E"])
+
+ top_level.add_ports(drain_P_via.get_ports_list(), prefix="P_drain_")
+ top_level.add_ports(source_P_via.get_ports_list(), prefix="P_source_")
+
+ elif placement == "vertical_invert":
+ pfet_ref.mirror_y()
+ pfet_ref.movey(nfet_ref.ymin - ref_dimensions[1]/2 - pdk.util_max_metal_seperation()-0.5)
+
+ #via for input and output
+ drain_P_via = top_level << viam2m3
+ source_P_via = top_level << viam2m3
+
+ drain_P_via.move(nfet_ref.ports["multiplier_0_source_W"].center).movex(-0.75).movey((pfet_ref.ymin-pfet_ref.ymax)/1.7)
+ source_P_via.move(nfet_ref.ports["multiplier_0_drain_E"].center).movex(0.75).movey((pfet_ref.ymin-pfet_ref.ymax)/1.7)
+ top_level << c_route(pdk, pfet_ref.ports["multiplier_0_source_W"], nfet_ref.ports["multiplier_0_source_W"])
+ top_level << c_route(pdk, pfet_ref.ports["multiplier_0_drain_E"], nfet_ref.ports["multiplier_0_drain_E"])
+
+ top_level.add_ports(drain_P_via.get_ports_list(), prefix="P_drain_")
+ top_level.add_ports(source_P_via.get_ports_list(), prefix="P_source_")
+
+ elif placement == "horizontal":
+ nfet_ref.movex(pfet_ref.xmax + ref_dimensions[0]/2 + pdk.util_max_metal_seperation()+1)
+
+ #via for input and output
+ drain_P_via = top_level << viam2m3
+ source_P_via = top_level << viam2m3
+
+ source_P_via.move(pfet_ref.ports["multiplier_0_source_W"].center).movex(ref_dimensions[0]*0.9)
+ drain_P_via.move(pfet_ref.ports["multiplier_0_drain_E"].center).movex(ref_dimensions[0]*0.15)
+ top_level << straight_route(pdk, pfet_ref.ports["multiplier_0_source_E"], nfet_ref.ports["multiplier_0_source_W"])
+ top_level << straight_route(pdk, pfet_ref.ports["multiplier_0_drain_E"], nfet_ref.ports["multiplier_0_drain_W"])
+
+ top_level.add_ports(drain_P_via.get_ports_list(), prefix="P_drain_")
+ top_level.add_ports(source_P_via.get_ports_list(), prefix="P_source_")
+
+ else:
+ raise ValueError("Placement must be either 'mirror', 'vertical', 'vertical _inv', or 'horizontal'")
#Renaming Ports
top_level.add_ports(nfet_ref.get_ports_list(), prefix="N_")
@@ -216,6 +342,7 @@ def transmission_gate(
return component
+
if __name__ == "__main__":
# OLD EVAL CODE
# comp = transmission_gate(sky130)
diff --git a/src/glayout/routing/Z_route.py b/src/glayout/routing/Z_route.py
new file mode 100644
index 00000000..5fb49ef5
--- /dev/null
+++ b/src/glayout/routing/Z_route.py
@@ -0,0 +1,147 @@
+from gdsfactory.cell import cell
+from gdsfactory.component import Component
+from gdsfactory.port import Port
+from glayout.pdk.mappedpdk import MappedPDK
+from typing import Optional, Union
+from glayout.primitives.via_gen import via_stack, via_array
+from glayout.util.comp_utils import evaluate_bbox, align_comp_to_port, to_decimal, to_float, prec_ref_center, get_primitive_rectangle
+from glayout.util.port_utils import rename_ports_by_orientation, rename_ports_by_list, print_ports, assert_port_manhattan, assert_ports_perpindicular
+from decimal import Decimal
+from glayout.routing.straight_route import straight_route
+from glayout.routing.c_route import c_route, __fill_empty_viastack__macro
+from glayout.routing.L_route import L_route
+from glayout.util.comp_utils import evaluate_bbox, get_primitive_rectangle, to_float, prec_ref_center
+from glayout.util.port_utils import add_ports_perimeter, rename_ports_by_orientation, rename_ports_by_list, print_ports, set_port_width, set_port_orientation, get_orientation
+from pydantic import validate_arguments
+from gdsfactory.snap import snap_to_grid
+
+@cell
+def z_route(
+ pdk: MappedPDK,
+ edge1: Port,
+ edge2: Port,
+ width1: Optional[float] = None,
+ width2: Optional[float] = None,
+ cwidth: Optional[float] = None,
+ glayer1: Optional[str] = None,
+ glayer2: Optional[str] = None,
+ cglayer: Optional[str] = None,
+ fullbottom: bool = True,
+ extra_vias: Optional[bool] = False,
+ ) -> Component:
+ """
+ Creates a Z shaped route between 2 ports.
+ edge1| ------
+ |
+ |
+ ------ |edge2
+
+ Requires:
+ - ports be vertical or horizontal
+ - edges need to be on the same or opposite orientation (e.g south-north and east-west)
+
+ ****NOTE: does no drc error checking
+ args:
+ pdk = pdk to use
+ edge1 = first port
+ edge2 = second port
+ width1 = optional will default to vertical edge width if None
+ width2 = optional will default to horizontal edge width if None
+ hglayer = glayer for vertical route. Defaults to the layer of the edge oriented N/S
+ vglayer = glayer for horizontal route. Defaults to the layer of the edge oriented E/W
+ viaoffset = push the via away from both edges so that inside corner aligns with via corner
+ ****via offset can also be specfied as a tuple(bool,bool): movex? if viaoffset[0] and movey? if viaoffset[1]
+ fullbottom = fullbottom option for via
+ """
+
+ assert_port_manhattan([edge1, edge2])
+ if edge1.orientation % 180 != edge2.orientation % 180:
+ raise ValueError(
+ f"Z-route error: Port should be paralel. "
+ f"Found: edge1={edge1.orientation}°, edge2={edge2.orientation}°"
+ )
+
+ if edge1.orientation == edge2.orientation:
+ edge2.orientation = (edge2.orientation + 180) % 360
+
+ pdk.activate()
+ Zroute = Component()
+ edge1_is_EW = bool(round(edge1.orientation + 90) % 180)
+ diff_y = abs(edge1.y -edge2.y)
+ diff_x = abs(edge1.x -edge2.x)
+ if glayer1 is None:
+ glayer1 = pdk.layer_to_glayer(edge1.layer)
+ if glayer2 is None:
+ glayer2 = pdk.layer_to_glayer(edge2.layer)
+ glayer_plusone = "met" + str(int(glayer1[-1])+1)
+ cglayer = cglayer if cglayer else glayer_plusone
+
+
+ hdim_center = float(to_decimal(edge1.center[0]) - to_decimal(edge2.center[0]))
+ vdim_center = float(to_decimal(edge2.center[1]) - to_decimal(edge1.center[1]))
+
+ viastack1 = via_stack(pdk,glayer1,cglayer,fullbottom=fullbottom,assume_bottom_via=True,fulltop=True)
+ viastack1_dims = evaluate_bbox(viastack1,True)
+
+ if edge1_is_EW:
+ # horizontal z route
+ width2 = width2 if width2 else edge1.width
+ width1 = width1 if width1 else edge2.width
+ center = snap_to_grid((edge1.x + edge2.x) / 2)
+ end_pos = snap_to_grid(edge2.y)
+ else:
+ width1 = width1 if width1 else edge1.width
+ width2 = width2 if width2 else width1
+ end_pos = snap_to_grid((edge1.y + edge2.y) / 2)
+ center = snap_to_grid(edge2.x)
+ cwidth = cwidth if cwidth else min(width1,width2)
+ if extra_vias:
+ #condition checking for multiple vias at first intermediate node,
+ if round(edge1.orientation) == 0 or round(edge1.orientation) == 180:
+ use_arr1 = viastack1_dims[0] < cwidth or viastack1_dims[1] < width1
+ if round(edge1.orientation) == 90 or round(edge1.orientation) == 270:
+ use_arr1 = viastack1_dims[0] < width1 or viastack1_dims[1] < cwidth
+ #via array for the first node
+ if use_arr1:
+ if round(edge1.orientation) == 0 or round(edge1.orientation) == 180:
+ viastack1 = via_array(pdk, glayer1, cglayer, size=(cwidth,width1), fullbottom=fullbottom, no_exception=True)
+ if round(edge1.orientation) == 90 or round(edge1.orientation) == 270:
+ viastack1 = via_array(pdk, glayer2, cglayer, size=(width1,cwidth), fullbottom=fullbottom, no_exception=True)
+
+ if glayer1==cglayer and glayer2==cglayer:
+ viastack1 = __fill_empty_viastack__macro(pdk,glayer1)
+ elif glayer1 == cglayer:
+ viastack1 = __fill_empty_viastack__macro(pdk,glayer1,size=evaluate_bbox(viastack1))
+ elif glayer2 == cglayer:
+ viastack1 = __fill_empty_viastack__macro(pdk,glayer2,size=evaluate_bbox(viastack1))
+
+ me1 = prec_ref_center(viastack1)
+
+ Zroute.add(me1)
+ me1.move(destination=(center, end_pos))
+
+ if edge1_is_EW:
+ if edge1.y > edge2.y:
+ route_1 = L_route(pdk, edge1, me1.ports["top_met_S"], width1, cwidth, glayer1, cglayer)
+ else:
+ route_1 = L_route(pdk, edge1, me1.ports["top_met_N"], width1, cwidth, glayer1, cglayer)
+ else:
+ if edge1.x > edge2.x:
+ route_1 = L_route(pdk, edge1, me1.ports["top_met_E"], width1, cwidth, glayer1, cglayer)
+ else:
+ route_1 = L_route(pdk, edge1, me1.ports["top_met_W"], width1, cwidth, glayer1, cglayer)
+
+ if round(edge1.orientation) == 0:
+ route_2 = straight_route(pdk, edge2, me1.ports["top_met_E"], glayer2, width2, cglayer)
+ elif round(edge1.orientation) == 270:
+ route_2 = straight_route(pdk, edge2, me1.ports["top_met_S"], glayer2, width2, cglayer)
+ elif round(edge1.orientation) == 180:
+ route_2 = straight_route(pdk, edge2, me1.ports["top_met_W"], glayer2, width2, cglayer)
+ elif round(edge1.orientation) == 90:
+ route_2 = straight_route(pdk, edge2, me1.ports["top_met_N"], glayer2, width2, cglayer)
+
+ Zroute.add(route_2)
+ Zroute.add(route_1)
+ Zroute.add_ports(me1.get_ports_list(), prefix="con_v_")
+
+ return rename_ports_by_orientation(Zroute.flatten())
\ No newline at end of file