diff --git a/tutorial/glayout_tutorial_5T_OTA_part2.ipynb b/tutorial/glayout_tutorial_5T_OTA_part2.ipynb new file mode 100644 index 00000000..1d14a4f5 --- /dev/null +++ b/tutorial/glayout_tutorial_5T_OTA_part2.ipynb @@ -0,0 +1,971 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5f6dedb2-7032-4661-ae60-4f3a7ebe726b", + "metadata": {}, + "source": [ + "# Tutorial 2: 5-Transistor OTA LVS, PEX, and simulation using gLayout\n", + "\n", + "**By gLayout Team**\n", + "\n", + "**Content creators:** Adrian Sami Pratama, Dharma Anargya Jowandy" + ] + }, + { + "cell_type": "markdown", + "id": "2ac43d65-343e-4f91-ace4-9a6a250dc18b", + "metadata": {}, + "source": [ + "___\n", + "# Tutorial Objectives\n", + "\n", + "This notebook is a tutorial on-\n", + "\n", + "- **LVS (Layout Versus Schematic):** \n", + " You will learn how to compare your physical layout with the original schematic to ensure they are functionally identical. This process helps catch connectivity or device mismatches before fabrication.\n", + "\n", + "- **Extraction and Simulation:** \n", + " The tutorial will guide you through extracting parasitic elements from your layout, such as capacitance and resistance, to create a more accurate circuit model. You will then simulate the extracted netlist to analyze and verify the real-world performance of your design." + ] + }, + { + "attachments": { + "c3ac3bfb-ecf2-4168-83c4-f9514dc6e1ce.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "id": "212e6186-57a1-44bc-879d-a49057db104b", + "metadata": {}, + "source": [ + "## What is a 5T OTA?\n", + "\n", + "A 5-Transistor Operational Transconductance Amplifier (5T OTA) is one of the most fundamental analog circuit building blocks. It converts a differential input voltage into an output current. The circuit consists of:\n", + "\n", + "- **M1, M2** — Differential input pair (NMOS)\n", + "- **M3, M4** — Current mirror load (PMOS)\n", + "- **M5** — Tail current source (NMOS)\n", + "\n", + "Note that the sixth transistor (M6) is used for reference current input\n", + "\n", + "### Schematic Reference\n", + "\n", + "![image.png](attachment:c3ac3bfb-ecf2-4168-83c4-f9514dc6e1ce.png)" + ] + }, + { + "cell_type": "markdown", + "id": "091484d8-57a2-424f-a752-ab057290f153", + "metadata": {}, + "source": [ + "# **NetList generation and LVS**\n", + "let's go through the step by step procedure to generate LVS and DRC clean layout of a 5T OTA cell." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6654744d-e646-408a-85ab-5d3967dea400", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import subprocess\n", + "\n", + "# Run a shell, source .bashrc, then printenv\n", + "cmd = 'bash -c \"source ~/.bashrc && printenv\"'\n", + "result = subprocess.run(cmd, shell=True, text=True, capture_output=True)\n", + "env_vars = {}\n", + "for line in result.stdout.splitlines():\n", + " if '=' in line:\n", + " key, value = line.split('=', 1)\n", + " env_vars[key] = value\n", + "\n", + "# Now, update os.environ with these\n", + "os.environ.update(env_vars)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84912826-fbd5-4772-b0a4-0371872dd155", + "metadata": {}, + "outputs": [], + "source": [ + "from glayout import MappedPDK, sky130 , gf180\n", + "#from gdsfactory.cell import cell\n", + "from gdsfactory import Component\n", + "from gdsfactory.components import text_freetype, rectangle" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bc7007a-0211-4d40-860d-3c06706cd120", + "metadata": {}, + "outputs": [], + "source": [ + "from glayout import nmos, pmos\n", + "from glayout import via_stack\n", + "from glayout import rename_ports_by_orientation\n", + "from glayout import tapring" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b30913c0-26b9-44ff-819e-e1a9dc39335f", + "metadata": {}, + "outputs": [], + "source": [ + "from glayout.util.comp_utils import evaluate_bbox, prec_center, prec_ref_center, align_comp_to_port\n", + "from glayout.util.port_utils import add_ports_perimeter,print_ports\n", + "from glayout.util.snap_to_grid import component_snap_to_grid\n", + "from glayout.spice.netlist import Netlist" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08aab530-2236-4a81-b5c4-e43a8dd19f66", + "metadata": {}, + "outputs": [], + "source": [ + "from glayout.routing.straight_route import straight_route\n", + "from glayout.routing.c_route import c_route\n", + "from glayout.routing.L_route import L_route" + ] + }, + { + "cell_type": "markdown", + "id": "8ededba7-acb7-4eb5-9559-520b98fba95e", + "metadata": {}, + "source": [ + "## Demonstration of Basic Layout / Netlist Generation in SKY130 & GF180\n", + "\n", + "The code below is an example of basic layout / netlist generation for primitive cells. We also define some of the function below that we will use for viewing our netlist-generated layout (for comparison purposes)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c66926e1-c832-4e70-83cd-1d945a8f806e", + "metadata": {}, + "outputs": [], + "source": [ + "import gdstk\n", + "import svgutils.transform as sg\n", + "import IPython.display\n", + "from IPython.display import clear_output\n", + "import ipywidgets as widgets\n", + "\n", + "# Used to display the results in a grid (notebook only)\n", + "left = widgets.Output()\n", + "leftSPICE = widgets.Output()\n", + "right = widgets.Output()\n", + "rightSPICE = widgets.Output()\n", + "hide = widgets.Output()\n", + "\n", + "grid = widgets.GridspecLayout(1, 4)\n", + "grid[0, 0] = left\n", + "grid[0, 1] = leftSPICE\n", + "grid[0, 2] = right\n", + "grid[0, 3] = rightSPICE\n", + "display(grid)\n", + "\n", + "def display_gds(gds_file, scale = 3):\n", + " # Generate an SVG image\n", + " top_level_cell = gdstk.read_gds(gds_file).top_level()[0]\n", + " top_level_cell.write_svg('../../out.svg')\n", + "\n", + " # Scale the image for displaying\n", + " fig = sg.fromfile('../../out.svg')\n", + " fig.set_size((str(float(fig.width) * scale), str(float(fig.height) * scale)))\n", + " fig.save('../../out.svg')\n", + "\n", + " # Display the image\n", + " IPython.display.display(IPython.display.SVG('../../out.svg'))\n", + "\n", + "def display_component(component, scale = 3):\n", + " # Save to a GDS file\n", + " with hide:\n", + " component.write_gds(\"../../out.gds\")\n", + "\n", + " display_gds('../../out.gds', scale)\n", + "\n", + "with hide:\n", + " # Generate the sky130 component\n", + " component_sky130 = nmos(pdk = sky130, fingers=5)\n", + " # Generate the gf180 component\n", + " component_gf180 = nmos(pdk = gf180, fingers=5,with_dnwell=False)\n", + "\n", + "# Display the components' GDS and SPICE netlists\n", + "with left:\n", + " print('Skywater 130nm N-MOSFET (fingers = 5)')\n", + " display_component(component_sky130, scale=2)\n", + "with leftSPICE:\n", + " print('Skywater 130nm SPICE Netlist')\n", + " print(component_sky130.info['netlist'].generate_netlist())\n", + "\n", + "with right:\n", + " print('GF 180nm N-MOSFET (fingers = 5)')\n", + " display_component(component_gf180, scale=2)\n", + "with rightSPICE:\n", + " print('GF 180nm SPICE Netlist')\n", + " print(component_gf180.info['netlist'].generate_netlist())" + ] + }, + { + "cell_type": "markdown", + "id": "5c7a2bcc-090f-4ada-9424-c74a5787bb92", + "metadata": {}, + "source": [ + "## Python Code for 5T OTA\n", + "\n", + "Below is the python code for generating 5T OTA. We will need this to generate the spice netlist. This code is similar to the part 1 notebook of this tutorial, where we generated the layout of this cell. It only has some minor syntax changes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f19e0887-350c-44c9-9a34-bf04504c6208", + "metadata": {}, + "outputs": [], + "source": [ + "fivet_ota_code_string = \"\"\"\n", + "from glayout import MappedPDK, sky130 , gf180\n", + "# from gdsfactory.cell import cell\n", + "from gdsfactory import Component\n", + "from gdsfactory.components import text_freetype, rectangle\n", + "\n", + "from glayout import nmos, pmos\n", + "from glayout import via_stack\n", + "from glayout import rename_ports_by_orientation\n", + "from glayout import tapring\n", + "\n", + "from glayout.util.comp_utils import evaluate_bbox, prec_center, prec_ref_center, align_comp_to_port\n", + "from glayout.util.port_utils import add_ports_perimeter,print_ports\n", + "from glayout.util.snap_to_grid import component_snap_to_grid\n", + "from glayout.spice.netlist import Netlist\n", + "\n", + "from glayout.routing.straight_route import straight_route\n", + "from glayout.routing.c_route import c_route\n", + "from glayout.routing.L_route import L_route\n", + "\n", + "\n", + "###### Only Required for IIC-OSIC Docker\n", + "import os\n", + "import subprocess\n", + "\n", + "# Run a shell, source .bashrc, then printenv\n", + "cmd = 'bash -c \"source ~/.bashrc && printenv\"'\n", + "result = subprocess.run(cmd, shell=True, text=True, capture_output=True)\n", + "env_vars = {}\n", + "for line in result.stdout.splitlines():\n", + " if '=' in line:\n", + " key, value = line.split('=', 1)\n", + " env_vars[key] = value\n", + "\n", + "# Now, update os.environ with these\n", + "os.environ.update(env_vars)\n", + "\n", + "\n", + "def add_fivet_ota_labels(\n", + " fivet_ota_in: Component,\n", + " pdk: MappedPDK,\n", + ") -> Component:\n", + " fivet_ota_in.unlock()\n", + "\n", + " psize=(0.5,0.5)\n", + " # list that will contain all port/comp info\n", + " move_info = list()\n", + " # create labels and append to info list\n", + "\n", + " # vss\n", + " vsslabel = rectangle(layer=pdk.get_glayer(\"met2_pin\"),size=psize,centered=True).copy()\n", + " vsslabel.add_label(text=\"VSS\",layer=pdk.get_glayer(\"met2_label\"))\n", + " move_info.append((vsslabel,fivet_ota_in.ports[\"VSS_bottom_met_E\"],None))\n", + " #vss_ref = top_level << vsslabel;\n", + " \n", + " # vdd\n", + " vddlabel = rectangle(layer=pdk.get_glayer(\"met2_pin\"),size=psize,centered=True).copy()\n", + " vddlabel.add_label(text=\"VDD\",layer=pdk.get_glayer(\"met2_label\"))\n", + " move_info.append((vddlabel,fivet_ota_in.ports[\"VDD_bottom_met_E\"],None))\n", + " #vdd_ref = top_level << vddlabel;\n", + " \n", + " # vinp\n", + " vinplabel = rectangle(layer=pdk.get_glayer(\"met2_pin\"),size=psize,centered=True).copy()\n", + " vinplabel.add_label(text=\"VINP\",layer=pdk.get_glayer(\"met2_label\"))\n", + " move_info.append((vinplabel,fivet_ota_in.ports[\"VINP_bottom_met_E\"],None))\n", + " #vinp_ref = top_level << vinplabel;\n", + " \n", + " # vinn\n", + " vinnlabel = rectangle(layer=pdk.get_glayer(\"met2_pin\"),size=psize,centered=True).copy()\n", + " vinnlabel.add_label(text=\"VINN\",layer=pdk.get_glayer(\"met2_label\"))\n", + " move_info.append((vinnlabel,fivet_ota_in.ports[\"VINN_bottom_met_W\"],None))\n", + " #vinn_ref = top_level << vinnlabel;\n", + " \n", + " # vout\n", + " voutlabel = rectangle(layer=pdk.get_glayer(\"met2_pin\"),size=psize,centered=True).copy()\n", + " voutlabel.add_label(text=\"VOUT\",layer=pdk.get_glayer(\"met2_label\"))\n", + " move_info.append((voutlabel,fivet_ota_in.ports[\"VOUT_bottom_met_W\"],None))\n", + " #out_ref = top_level << outlabel;\n", + " \n", + " # in_cur\n", + " in_curlabel = rectangle(layer=pdk.get_glayer(\"met2_pin\"),size=psize,centered=True).copy()\n", + " in_curlabel.add_label(text=\"IN_CUR\",layer=pdk.get_glayer(\"met2_label\"))\n", + " move_info.append((in_curlabel,fivet_ota_in.ports[\"INCUR_bottom_met_W\"],None))\n", + " #in_cur_ref = top_level << in_curlabel;\n", + "\n", + " # move everything to position\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", + " fivet_ota_in.add(compref)\n", + " \n", + " return fivet_ota_in.flatten()\n", + "\n", + "# @cell\n", + "\n", + "def fivet_ota(\n", + " pdk: MappedPDK,\n", + " input_pair: dict = {\n", + " \"width\": 5.75,\n", + " \"length\": 0.4,\n", + " \"fingers\": 2,\n", + " \"multipliers\": 1,\n", + " \"device_type\": \"nmos\",\n", + " },\n", + " current_mirror: dict = {\n", + " \"width\": 8.45,\n", + " \"length\": 0.4,\n", + " \"fingers\": 5,\n", + " \"multipliers\": 1,\n", + " \"device_type\": \"pmos\",\n", + " },\n", + " tail_source: dict = {\n", + " \"width\": 9.55,\n", + " \"length\": 0.7,\n", + " \"fingers\": 5,\n", + " \"multipliers\": 1,\n", + " \"device_type\": \"nmos\",\n", + " },\n", + " layout_rules: dict = {\n", + " \"spacing\": 2.0,\n", + " \"routing_metal\": \"met2\",\n", + " \"dummy_devices\": True,\n", + " \"tie_layers\": (\"met2\", \"met1\"),\n", + " \"sd_rmult\": 1,\n", + " },\n", + " **kwargs\n", + ") -> Component:\n", + "\n", + " pdk.activate()\n", + "\n", + " fivet_ota_config = {\n", + " \"input_pair\": input_pair,\n", + " \"current_mirror\": current_mirror,\n", + " \"tail_source\": tail_source,\n", + " \"layout_rules\": layout_rules,\n", + " }\n", + "\n", + " nmos_kwargs = {\n", + " \"with_tie\": False,\n", + " \"with_dnwell\": False,\n", + " \"sd_route_topmet\": \"met2\",\n", + " \"gate_route_topmet\": \"met2\",\n", + " \"sd_route_left\": True,\n", + " \"rmult\": None,\n", + " \"gate_rmult\": 1,\n", + " \"interfinger_rmult\": 1,\n", + " \"substrate_tap_layers\": layout_rules[\"tie_layers\"],\n", + " \"dummy_routes\": True,\n", + " }\n", + "\n", + " pmos_kwargs = {\n", + " \"with_tie\": False,\n", + " \"dnwell\": False,\n", + " \"sd_route_topmet\": \"met2\",\n", + " \"gate_route_topmet\": \"met2\",\n", + " \"sd_route_left\": True,\n", + " \"rmult\": None,\n", + " \"gate_rmult\": 1,\n", + " \"interfinger_rmult\": 1,\n", + " \"substrate_tap_layers\": layout_rules[\"tie_layers\"],\n", + " \"dummy_routes\": True,\n", + " }\n", + " \n", + " #top level component\n", + " top_level = Component(name=\"fivet_ota\")\n", + "\n", + " def current_mirror(pdk, config):\n", + " cm_comp = Component(name=\"current_mirror\")\n", + " \n", + " # take config parameters\n", + " width = config[\"current_mirror\"][\"width\"]\n", + " length = config[\"current_mirror\"][\"length\"]\n", + " fingers = config[\"current_mirror\"][\"fingers\"]\n", + " multipliers = config[\"current_mirror\"][\"multipliers\"]\n", + " tie_layers = config[\"layout_rules\"][\"tie_layers\"]\n", + " sd_rmult = config[\"layout_rules\"][\"sd_rmult\"]\n", + " \n", + " # ============ Instantiate 2 PMOS ============\n", + " pfet_ref = pmos(pdk, width=width, length=length, fingers=fingers,\n", + " multipliers=multipliers,\n", + " with_substrate_tap=False,\n", + " with_dummy=(False, True),\n", + " tie_layers=tie_layers,\n", + " sd_rmult=sd_rmult,\n", + " **pmos_kwargs)\n", + " pfet_mir = pmos(pdk, width=width, length=length, fingers=fingers,\n", + " multipliers=multipliers,\n", + " with_substrate_tap=False,\n", + " with_dummy=(True, False),\n", + " tie_layers=tie_layers,\n", + " sd_rmult=sd_rmult,\n", + " **pmos_kwargs)\n", + " \n", + " cref_ref = cm_comp << pfet_ref\n", + " cmir_ref = cm_comp << pfet_mir\n", + " cref_ref.name = \"pfet_ref\"\n", + " cmir_ref.name = \"pfet_mir\"\n", + " \n", + " # ============ Placement ============\n", + " cref_ref.movex(evaluate_bbox(pfet_mir)[0] + pdk.util_max_metal_seperation())\n", + " \n", + " # ============ Tapring ============\n", + " tap_ring = tapring(pdk, \n", + " enclosed_rectangle=evaluate_bbox(\n", + " cm_comp.flatten(),\n", + " padding=pdk.get_grule(\"nwell\", \"active_diff\")[\"min_enclosure\"]),\n", + " sdlayer=\"n+s/d\", \n", + " horizontal_glayer=tie_layers[0], \n", + " vertical_glayer=tie_layers[1]) \n", + " shift_amount = -prec_center(cm_comp.flatten())[0]\n", + " tring_ref = cm_comp << tap_ring\n", + " tring_ref.movex(destination=shift_amount)\n", + " \n", + " # Add nwell padding to close gap between nwell\n", + " cm_comp.add_padding(layers=(pdk.get_glayer(\"nwell\"),), default=1)\n", + " \n", + " # ============ Internal routing ============\n", + " cm_comp << straight_route(pdk, cref_ref.ports[\"multiplier_0_source_E\"],\n", + " cmir_ref.ports[\"multiplier_0_source_E\"])\n", + " cm_comp << straight_route(pdk, cref_ref.ports[\"multiplier_0_gate_E\"],\n", + " cmir_ref.ports[\"multiplier_0_gate_E\"])\n", + " cm_comp << c_route(pdk, cref_ref.ports[\"multiplier_0_gate_E\"],\n", + " cref_ref.ports[\"multiplier_0_drain_E\"])\n", + "\n", + " # Route dummy to tapring (We have to route it manually since we generated the tapring manually)\n", + " cm_comp << straight_route(pdk, cmir_ref.ports[\"multiplier_0_dummy_L_gsdcon_top_met_E\"],\n", + " tring_ref.ports[\"W_top_met_W\"])\n", + " cm_comp << straight_route(pdk, cref_ref.ports[\"multiplier_0_dummy_R_gsdcon_top_met_W\"],\n", + " tring_ref.ports[\"E_top_met_E\"])\n", + " \n", + " # ============ Expose ports ============\n", + " cm_comp.add_ports(cref_ref.get_ports_list(), prefix=\"REF_\")\n", + " cm_comp.add_ports(cmir_ref.get_ports_list(), prefix=\"MIR_\")\n", + " cm_comp.add_ports(tring_ref.get_ports_list(), prefix=\"TRING_\")\n", + " cm_comp.info.update({\"pfet_ref\": pfet_ref, \"pfet_mir\": pfet_mir})\n", + " \n", + " return cm_comp\n", + " \n", + " # ============ Add to top level ============\n", + " cm = current_mirror(pdk, fivet_ota_config)\n", + " cm_ref = top_level << cm\n", + " cm_ref.name = \"current_mirror\"\n", + "\n", + " def diff_pair(pdk, config):\n", + " dp_comp = Component(name=\"diff_pair\")\n", + " \n", + " # take config parameters\n", + " width = config[\"input_pair\"][\"width\"]\n", + " length = config[\"input_pair\"][\"length\"]\n", + " fingers = config[\"input_pair\"][\"fingers\"]\n", + " multipliers = config[\"input_pair\"][\"multipliers\"]\n", + " tie_layers = config[\"layout_rules\"][\"tie_layers\"]\n", + " sd_rmult = config[\"layout_rules\"][\"sd_rmult\"]\n", + " \n", + " # ============ Instantiate 2 NMOS ============\n", + " m1 = nmos(pdk, width=width, length=length, fingers=fingers,\n", + " multipliers=multipliers,\n", + " with_substrate_tap=False,\n", + " with_dummy=(True , False),\n", + " tie_layers=tie_layers,\n", + " sd_rmult=sd_rmult,\n", + " **nmos_kwargs)\n", + " m2 = nmos(pdk, width=width, length=length, fingers=fingers,\n", + " multipliers=multipliers,\n", + " with_substrate_tap=False,\n", + " with_dummy=(False,True),\n", + " tie_layers=tie_layers,\n", + " sd_rmult=sd_rmult,\n", + " **nmos_kwargs)\n", + " \n", + " m1_ref = dp_comp << m1\n", + " m2_ref = dp_comp << m2\n", + " m1_ref.name = \"M1\" # M1 is the negative input diffpair\n", + " m2_ref.name = \"M2\" # M2 is the positive input diffpair\n", + " \n", + " # ============ Placement ============\n", + " ref_dimensions = evaluate_bbox(m1)\n", + " m2_ref.movex(m1_ref.xmax)\n", + " m2_ref.movex(ref_dimensions[0]/2)\n", + " m2_ref.movex(pdk.util_max_metal_seperation())\n", + " \n", + " # ============ Internal routing ============\n", + " dp_comp << straight_route(pdk,\n", + " m1_ref.ports[\"multiplier_0_source_E\"],\n", + " m2_ref.ports[\"multiplier_0_source_W\"])\n", + " \n", + " # ============ Tapring ============\n", + " tap_ring = tapring(pdk, enclosed_rectangle=evaluate_bbox(\n", + " dp_comp.flatten(),\n", + " padding=pdk.get_grule(\"nwell\", \"active_diff\")[\"min_enclosure\"]))\n", + " shift_amount = -prec_center(dp_comp.flatten())[0]\n", + " tring_ref = dp_comp << tap_ring\n", + " tring_ref.movex(destination=shift_amount)\n", + "\n", + " # Route dummy to tapring (We have to route it manually since we generated the tapring manually)\n", + " dp_comp << straight_route(pdk, m1_ref.ports[\"multiplier_0_dummy_L_gsdcon_top_met_E\"],\n", + " tring_ref.ports[\"W_top_met_W\"])\n", + " dp_comp << straight_route(pdk, m2_ref.ports[\"multiplier_0_dummy_R_gsdcon_top_met_W\"],\n", + " tring_ref.ports[\"E_top_met_E\"])\n", + " \n", + " # ============ Expose ports ============\n", + " dp_comp.add_ports(m1_ref.get_ports_list(), prefix=\"M1_\")\n", + " dp_comp.add_ports(m2_ref.get_ports_list(), prefix=\"M2_\")\n", + " dp_comp.add_ports(tring_ref.get_ports_list(), prefix=\"TRING_\")\n", + " dp_comp.info.update({\"M1\": m1, \"M2\": m2})\n", + " \n", + " return dp_comp\n", + " \n", + " # ============ Test ============\n", + " dp = diff_pair(pdk, fivet_ota_config)\n", + " dp_ref = top_level << dp\n", + " dp_ref.name = \"diff_pair\"\n", + " \n", + " def tail_current(pdk, config):\n", + " tail_comp = Component(name=\"tail_current\")\n", + " \n", + " # take config parameters\n", + " width = config[\"tail_source\"][\"width\"]\n", + " length = config[\"tail_source\"][\"length\"]\n", + " fingers = config[\"tail_source\"][\"fingers\"]\n", + " multipliers = config[\"tail_source\"][\"multipliers\"]\n", + " tie_layers = config[\"layout_rules\"][\"tie_layers\"]\n", + " sd_rmult = config[\"layout_rules\"][\"sd_rmult\"]\n", + " \n", + " # ============ Instantiate 2 NMOS ============\n", + " nfet_ref = nmos(pdk, width=width, length=length, fingers=fingers,\n", + " multipliers=multipliers,\n", + " with_substrate_tap=False,\n", + " with_dummy=(False, True),\n", + " tie_layers=tie_layers,\n", + " sd_rmult=sd_rmult,\n", + " **nmos_kwargs)\n", + " nfet_mir = nmos(pdk, width=width, length=length, fingers=fingers,\n", + " multipliers=multipliers,\n", + " with_substrate_tap=False,\n", + " with_dummy=(True, False),\n", + " tie_layers=tie_layers,\n", + " sd_rmult=sd_rmult,\n", + " **nmos_kwargs)\n", + " \n", + " tref_ref = tail_comp << nfet_ref\n", + " tmir_ref = tail_comp << nfet_mir\n", + " tref_ref.name = \"nfet_ref\"\n", + " tmir_ref.name = \"nfet_mir\"\n", + " \n", + " # ============ Placement ============\n", + " tref_ref.movex(evaluate_bbox(nfet_mir)[0] + pdk.util_max_metal_seperation())\n", + " \n", + " # ============ Tapring ============\n", + " tap_ring = tapring(pdk, enclosed_rectangle=evaluate_bbox(\n", + " tail_comp.flatten(),\n", + " padding=pdk.get_grule(\"nwell\", \"active_diff\")[\"min_enclosure\"]))\n", + " shift_amount = -prec_center(tail_comp.flatten())[0]\n", + " tring_ref = tail_comp << tap_ring\n", + " tring_ref.movex(destination=shift_amount)\n", + " \n", + " # ============ Internal routing ============\n", + " tail_comp << straight_route(pdk, tref_ref.ports[\"multiplier_0_source_E\"],\n", + " tmir_ref.ports[\"multiplier_0_source_E\"])\n", + " tail_comp << straight_route(pdk, tref_ref.ports[\"multiplier_0_gate_E\"],\n", + " tmir_ref.ports[\"multiplier_0_gate_E\"])\n", + " tail_comp << c_route(pdk, tref_ref.ports[\"multiplier_0_gate_E\"],\n", + " tref_ref.ports[\"multiplier_0_drain_E\"])\n", + "\n", + " # Route dummy to tapring (We have to route it manually since we generated the tapring manually)\n", + " tail_comp << straight_route(pdk, tmir_ref.ports[\"multiplier_0_dummy_L_gsdcon_top_met_E\"],\n", + " tring_ref.ports[\"W_top_met_W\"])\n", + " tail_comp << straight_route(pdk, tref_ref.ports[\"multiplier_0_dummy_R_gsdcon_top_met_W\"],\n", + " tring_ref.ports[\"E_top_met_E\"])\n", + " \n", + " # ============ Expose ports ============\n", + " tail_comp.add_ports(tref_ref.get_ports_list(), prefix=\"REF_\")\n", + " tail_comp.add_ports(tmir_ref.get_ports_list(), prefix=\"MIR_\")\n", + " tail_comp.add_ports(tring_ref.get_ports_list(), prefix=\"TRING_\")\n", + " tail_comp.info.update({\"nfet_ref\": nfet_ref, \"nfet_mir\": nfet_mir})\n", + " \n", + " return tail_comp\n", + " \n", + " # ============ Test ============\n", + " tail = tail_current(pdk, fivet_ota_config)\n", + " tail_ref = top_level << tail\n", + " tail_ref.name = \"tail_current\"\n", + " \n", + " # Evaluate bbox for every sub circuit\n", + " dp_dimensions = evaluate_bbox(dp)\n", + " tail_dimensions = evaluate_bbox(tail)\n", + " \n", + " # diff_pair → place it below current_mirror\n", + " dp_ref.movey(cm_ref.ymin - dp_dimensions[1]/2 - pdk.util_max_metal_seperation())\n", + " \n", + " # tail_current → place it below diff_pair\n", + " tail_ref.movey(dp_ref.ymin - tail_dimensions[1]/2 - pdk.util_max_metal_seperation())\n", + " \n", + " # Use center coordinates\n", + " dp_ref.movex(cm_ref.center[0] - dp_ref.center[0])\n", + " tail_ref.movex(cm_ref.center[0] - tail_ref.center[0])\n", + "\n", + " viam2m3 = via_stack(pdk, \"met2\", \"met3\", centered=True)\n", + " viam1m2 = via_stack(pdk, \"met1\", \"met2\", centered=True)\n", + " \n", + " # Left current mirror drain via\n", + " drain_mir_via = top_level << viam2m3\n", + " drain_mir_via.move(cm_ref.ports[\"MIR_multiplier_0_drain_W\"].center).movex(-5)\n", + " \n", + " # Right current mirror drain via\n", + " drain_ref_via = top_level << viam2m3\n", + " drain_ref_via.move(cm_ref.ports[\"REF_multiplier_0_drain_E\"].center).movex(5)\n", + " \n", + " # Left diffpair drain via\n", + " drain_m1_via = top_level << viam2m3\n", + " drain_m1_via.move(dp_ref.ports[\"M1_multiplier_0_drain_W\"].center).movex(-5)\n", + " drain_m1_via.movex(drain_mir_via.x - drain_m1_via.x)\n", + " \n", + " # Right diffpair drain via\n", + " drain_m2_via = top_level << viam2m3\n", + " drain_m2_via.move(dp_ref.ports[\"M2_multiplier_0_drain_E\"].center).movex(5)\n", + " drain_m2_via.movex(drain_ref_via.x - drain_m2_via.x)\n", + " \n", + " # Diffpair source via\n", + " source_m1_via = top_level << viam2m3\n", + " source_m1_via.move(dp_ref.ports[\"M1_multiplier_0_source_W\"].center)\n", + " \n", + " # Current tail drain via\n", + " drain_sink_via = top_level << viam2m3\n", + " drain_sink_via.move(tail_ref.ports[\"MIR_multiplier_0_drain_W\"].center)\n", + " source_m1_via.movex(drain_sink_via.x - source_m1_via.x)\n", + "\n", + " # 1. Right current mirror (drain_mir_via) → drain M1 diff pair\n", + " top_level << straight_route(pdk,\n", + " drain_mir_via.ports[\"top_met_N\"],\n", + " drain_m1_via.ports[\"top_met_S\"])\n", + " \n", + " # 2. Left current mirror (drain_ref_via) → drain M2 diff pair\n", + " top_level << straight_route(pdk,\n", + " drain_ref_via.ports[\"top_met_N\"],\n", + " drain_m2_via.ports[\"top_met_S\"])\n", + " \n", + " # 3. Source common diff pair → drain tail\n", + " top_level << straight_route(pdk,\n", + " source_m1_via.ports[\"top_met_N\"],\n", + " drain_sink_via.ports[\"top_met_S\"])\n", + "\n", + " # MIR drain → drain_ref_via\n", + " top_level << straight_route(pdk,\n", + " cm_ref.ports[\"MIR_multiplier_0_drain_E\"],\n", + " drain_mir_via.ports[\"bottom_met_W\"])\n", + " \n", + " # REF drain → drain_mir_via\n", + " top_level << straight_route(pdk,\n", + " cm_ref.ports[\"REF_multiplier_0_drain_W\"],\n", + " drain_ref_via.ports[\"bottom_met_E\"])\n", + " \n", + " # M1 drain → drain_l_pair_via\n", + " top_level << straight_route(pdk,\n", + " dp_ref.ports[\"M1_multiplier_0_drain_E\"],\n", + " drain_m1_via.ports[\"bottom_met_W\"])\n", + " \n", + " # M2 drain → drain_r_pair_via\n", + " top_level << straight_route(pdk,\n", + " dp_ref.ports[\"M2_multiplier_0_drain_W\"],\n", + " drain_m2_via.ports[\"bottom_met_E\"])\n", + " \n", + " top_level << straight_route(pdk,\n", + " dp_ref.ports[\"M2_multiplier_0_source_E\"],\n", + " drain_sink_via.ports[\"bottom_met_W\"])\n", + " \n", + " # Route VDD to N+ tapring\n", + " top_level << straight_route(pdk,\n", + " cm_ref.ports[\"MIR_multiplier_0_source_E\"],\n", + " cm_ref.ports[\"TRING_W_top_met_W\"])\n", + " \n", + " # Route VSS to P+ tapring\n", + " top_level << straight_route(pdk,\n", + " tail_ref.ports[\"REF_multiplier_0_source_E\"],\n", + " tail_ref.ports[\"TRING_W_top_met_W\"])\n", + " \n", + " # Route tail tapring to diffpair tapring\n", + " top_level << straight_route(pdk,\n", + " dp_ref.ports[\"TRING_S_top_met_N\"],\n", + " tail_ref.ports[\"TRING_N_top_met_S\"])\n", + "\n", + " # Expose signal ports \n", + " top_level.add_port(name=\"VSS_bottom_met_E\", port=tail_ref.ports[\"REF_multiplier_0_source_E\"])\n", + " top_level.add_port(name=\"VDD_bottom_met_E\", port=cm_ref.ports[\"MIR_multiplier_0_source_E\"])\n", + " top_level.add_port(name=\"VINP_bottom_met_E\", port=dp_ref.ports[\"M2_multiplier_0_gate_E\"])\n", + " top_level.add_port(name=\"VINN_bottom_met_W\", port=dp_ref.ports[\"M1_multiplier_0_gate_W\"])\n", + " top_level.add_port(name=\"VOUT_bottom_met_W\", port=drain_mir_via.ports[\"top_met_W\"])\n", + " top_level.add_port(name=\"INCUR_bottom_met_W\", port=tail_ref.ports[\"MIR_multiplier_0_gate_W\"])\n", + "\n", + " top_level.info.update({\n", + " \"m1\": dp.info[\"M1\"],\n", + " \"m2\": dp.info[\"M2\"],\n", + " \"pfet_ref\": cm.info[\"pfet_ref\"],\n", + " \"pfet_mir\": cm.info[\"pfet_mir\"],\n", + " \"nfet_tail_ref\": tail.info[\"nfet_ref\"],\n", + " \"nfet_tail_mir\": tail.info[\"nfet_mir\"],\n", + " })\n", + " return component_snap_to_grid(rename_ports_by_orientation(top_level))\n", + "\n", + "if __name__ == \"__main__\":\n", + " comp = fivet_ota(gf180)\n", + " comp = add_fivet_ota_labels(comp, gf180)\n", + " comp.name = \"FIVET_OTA\"\n", + " comp.write_gds('out_fivet_ota.gds')\n", + " comp.show()\n", + " print(\"...Running DRC...\")\n", + " drc_result = gf180.drc_magic(comp, \"FIVET_OTA\")\n", + "\n", + "\"\"\"\n", + "\n", + "fivet_ota_init_string = \"\"\"\n", + "### Glayout 5T OTA Cell.\n", + "\n", + "from .my_fivet_ota import fivet_ota, add_fivet_ota_labels\n", + "\n", + "__all__ = [\n", + " 'fivet_ota',\n", + " 'add_fivet_ota_labels',\n", + "]\n", + "\"\"\"\n", + "\n", + "directory = \"../../FIVET_OTA/\"\n", + "os.makedirs(directory, exist_ok=True)\n", + "\n", + "# Save to a .py file\n", + "with open(directory + \"my_fivet_ota.py\", \"w\") as file:\n", + " file.write(fivet_ota_code_string)\n", + "\n", + "with open(directory + \"__init__.py\", \"w\") as file:\n", + " file.write(fivet_ota_init_string)" + ] + }, + { + "cell_type": "markdown", + "id": "52a013c8-c989-4918-a973-f74bc300784a", + "metadata": {}, + "source": [ + "Import the python code to this notebook" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07910ef2-f3bc-4886-828b-8f38f40d8c40", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "from pathlib import Path\n", + "sys.path.append(os.path.abspath(\"../../FIVET_OTA\"))\n", + "from my_fivet_ota import fivet_ota, add_fivet_ota_labels" + ] + }, + { + "cell_type": "markdown", + "id": "3e9ad5c7-6fa8-485f-9350-1ff266278146", + "metadata": {}, + "source": [ + "## Spice Netlist Generation\n", + "\n", + "We generate spice netlist (schematic) with the code below. First, we have to access the sub cells (the nfets and pfets) with `.info` method. After that, we define the input and output pins with `Netlist()` function. We connect the fets with `.connect_netlist()` method. Note that what we mean by spice netlist is the schematic netlist (we will use these words interchangeably)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a07b0e6-e02b-48f7-8b03-86c888c3371e", + "metadata": {}, + "outputs": [], + "source": [ + "def fivet_ota_netlist(ota_in: Component) -> Component:\n", + " m1 = ota_in.info[\"m1\"]\n", + " m2 = ota_in.info[\"m2\"]\n", + " pfet_ref = ota_in.info[\"pfet_ref\"]\n", + " pfet_mir = ota_in.info[\"pfet_mir\"]\n", + " nfet_tail_ref = ota_in.info[\"nfet_tail_ref\"]\n", + " nfet_tail_mir = ota_in.info[\"nfet_tail_mir\"]\n", + "\n", + " # Define the top netlist, also the input and output ports\n", + " netlist = Netlist(\n", + " circuit_name='FIVET_OTA_1',\n", + " nodes=['VINP', 'VINN', 'VOUT', 'VDD', 'VSS', 'IN_CUR']\n", + " )\n", + "\n", + " # Connect the netlist\n", + " netlist.connect_netlist(m1.info['netlist'], [('D', 'VOUT'), ('G', 'VINN'), ('S', 'VTAIL'), ('B', 'VSS')])\n", + " netlist.connect_netlist(m2.info['netlist'], [('D', 'VMID'), ('G', 'VINP'), ('S', 'VTAIL'), ('B', 'VSS')])\n", + " netlist.connect_netlist(pfet_ref.info['netlist'], [('D', 'VMID'), ('G', 'VMID'), ('S', 'VDD'), ('B', 'VDD')])\n", + " netlist.connect_netlist(pfet_mir.info['netlist'], [('D', 'VOUT'), ('G', 'VMID'), ('S', 'VDD'), ('B', 'VDD')])\n", + " netlist.connect_netlist(nfet_tail_ref.info['netlist'], [('D','IN_CUR'), ('G','IN_CUR'), ('S','VSS'), ('B','VSS')])\n", + " netlist.connect_netlist(nfet_tail_mir.info['netlist'], [('D','VTAIL'), ('G','IN_CUR'), ('S','VSS'), ('B','VSS')])\n", + "\n", + " ota_in.info['netlist'] = netlist\n", + " return ota_in\n", + "\n", + "# This is an example of using the function to generate our spice netlist with specified properties\n", + "# Notice that we flattened the multi finger into one finger. Use the total width of the finger for the schematic netlist\n", + "my_ota = fivet_ota_netlist(fivet_ota((gf180),\n", + " input_pair = {\n", + " \"width\": 11.5,\n", + " \"length\": 0.4,\n", + " \"fingers\": 1,\n", + " \"multipliers\": 1\n", + " },\n", + " current_mirror = {\n", + " \"width\": 42.25,\n", + " \"length\": 0.4,\n", + " \"fingers\": 1,\n", + " \"multipliers\": 1\n", + " },\n", + " tail_source = {\n", + " \"width\": 47.75,\n", + " \"length\": 0.7,\n", + " \"fingers\": 1,\n", + " \"multipliers\": 1\n", + " })\n", + " )\n", + "my_ota = add_fivet_ota_labels(my_ota, gf180)\n", + "my_ota.name = \"FIVET_OTA_1\"\n", + "my_ota.write_gds('out_OTA.gds') # For writing the generated schematic netlist into gds layout\n", + "my_ota.show()\n", + "print(my_ota.info['netlist'].generate_netlist())" + ] + }, + { + "cell_type": "markdown", + "id": "d83c9cef-4332-49d4-af9f-3b4c4bea2023", + "metadata": {}, + "source": [ + "Print port list" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78bce88a-b817-442b-a47f-f65cf9ee8d5e", + "metadata": {}, + "outputs": [], + "source": [ + "# Check port list\n", + "comp = fivet_ota(gf180)\n", + "print_ports(comp)" + ] + }, + { + "cell_type": "markdown", + "id": "8dc53c6e-e39b-4fd4-ae21-4aa66c03e47b", + "metadata": {}, + "source": [ + "## Run LVS\n", + "\n", + "Below is the example of how to run the LVS. There were property errors when using multi fingers for schematic netlist, so better just make it into one finger and use the total width for your schematic netlist." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2311f1ca-3c76-447f-9603-7349ec87f3f5", + "metadata": {}, + "outputs": [], + "source": [ + "# Generate the schematic netlist, just as the previous step\n", + "ota = fivet_ota_netlist(add_fivet_ota_labels(fivet_ota((gf180),\n", + " input_pair = {\n", + " \"width\": 11.5,\n", + " \"length\": 0.4,\n", + " \"fingers\": 1,\n", + " \"multipliers\": 1\n", + " },\n", + " current_mirror = {\n", + " \"width\": 42.25,\n", + " \"length\": 0.4,\n", + " \"fingers\": 1,\n", + " \"multipliers\": 1\n", + " },\n", + " tail_source = {\n", + " \"width\": 47.75,\n", + " \"length\": 0.7,\n", + " \"fingers\": 1,\n", + " \"multipliers\": 1\n", + " }), gf180))\n", + "ota.name = \"fivet_OTA\"\n", + "ota_gds = ota.write_gds(\"fivet_OTA.gds\") # For writing the generated schematic netlist into gds layout\n", + "display_gds(ota_gds)\n", + "ota.show()\n", + "netgen_lvs_result = gf180.lvs_netgen(ota, ota.name)" + ] + }, + { + "cell_type": "markdown", + "id": "710d8e22-8f9a-44ee-9f25-0d252b674063", + "metadata": {}, + "source": [ + "# PEX and Post Layout Simulation" + ] + }, + { + "cell_type": "markdown", + "id": "a8e174d3-18dd-4a56-9ddc-8d82ed41ab9c", + "metadata": {}, + "source": [ + "> 🚧 **Under Construction**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88911f0e-f522-4930-8fd9-fc9d19219176", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "GLdev", + "language": "python", + "name": "gldev" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.20" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}