From c334af54d9a7cdddbd9c471588c1dc59e14958d3 Mon Sep 17 00:00:00 2001 From: AdrianSPratama <16523037@std.stei.itb.ac.id> Date: Sun, 3 May 2026 16:23:41 +0700 Subject: [PATCH] feat part 2 of 5T OTA tutorial notebook In this part 2 5T OTA tutorial notebook, we did LVS, PEX, and post layout simulation. Only LVS is finished, the others are still not finished. This cell is already DRC and LVS clean --- tutorial/glayout_tutorial_5T_OTA_part2.ipynb | 971 +++++++++++++++++++ 1 file changed, 971 insertions(+) create mode 100644 tutorial/glayout_tutorial_5T_OTA_part2.ipynb 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 +}