diff --git a/docs/source/examples.rst b/docs/source/examples.rst
index 734d1e2ed..f3db49961 100644
--- a/docs/source/examples.rst
+++ b/docs/source/examples.rst
@@ -10,3 +10,4 @@ Prefer to learn by example? These notebooks give a practical overview on how to
notebooks/temp-sense-gen/temp_sense_genCollab.ipynb
notebooks/ldo-gen/LDO_notebook.ipynb
+ notebooks/glayout/glayout_opamp.ipynb
diff --git a/docs/source/notebooks/glayout/glayout_opamp.ipynb b/docs/source/notebooks/glayout/glayout_opamp.ipynb
new file mode 100644
index 000000000..9955c043e
--- /dev/null
+++ b/docs/source/notebooks/glayout/glayout_opamp.ipynb
@@ -0,0 +1,803 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "aK2t7aSWNojQ"
+ },
+ "source": [
+ "# GLayout: PDK-Agnostic P-Cell Based Chip Layout Generation With Reinforcement Learning Optimization\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "view-in-github"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "xjDewsT5Y4lP"
+ },
+ "source": [
+ "```\n",
+ "OpenFASOC Team, November 2023\n",
+ "SPDX-License-Identifier: Apache-2.0\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "L6Ck4z5ujN_k"
+ },
+ "source": [
+ "\n",
+ "|Name|Affiliation| IEEE Member | SSCS Member |\n",
+ "|:-----------------:|:----------:|:----------:|:----------:|\n",
+ "| Harsh Khandeparkar| University of Michigan + Indian Institute of Technology Kharagpur | No | No |\n",
+ "| Anhang Li | University of Michigan | Yes | No |\n",
+ "| Ali Hammoud | University of Michigan | Yes | No |\n",
+ "| Ayushman Tripathi | University of Michigan | No | No |\n",
+ "| Wen Tian | University of Michigan | Yes | No |\n",
+ "| Mehdi Saligane (Advisor) | University of Michigan | Yes | Yes |\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "-Xp4cEjkeHIx"
+ },
+ "source": [
+ "# Introduction\n",
+ "Welcome!\n",
+ "This notebook serves as an introduction to the GDSFactory-based layout automation tool **GLayout** and an example two-stage Operational Amplifier (Op-Amp) generator, as a part of [OpenFASoC](https://github.com/idea-fasoc/OpenFASOC).\n",
+ "\n",
+ "## GDSFactory\n",
+ "[GDSFactory](https://gdsfactory.github.io/gdsfactory/index.html) is a Python library for designing integrated circuit layouts in Python and save it directly in the [GDSII](https://en.wikipedia.org/wiki/GDSII) format, and run DRC (Design Rule Checking) and LVS (Layout v/s Schematic) verification, or simulation.\n",
+ "\n",
+ "## GLayout\n",
+ "[GLayout](https://github.com/idea-fasoc/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/) is a layout automation python package which generates _DRC clean_ circuit layouts and SPICE netlists for any PDK (Process Design Kit). It is composed of two main parts: the _generic PDK framework_, and the _circuit generators_.\n",
+ "\n",
+ "The generic PDK framework allows for describing any PDK in a standardized format, defined by the `MappedPDK` class. The generators are Python functions that take as arguments a `MappedPDK` object, and a set of optional layout parameters to produce a DRC (Design Rule Checking) clean layout of a particular circuit design and the pre-PEX (Parasitic Extraction) SPICE netlist, for LVS (Layout v/s Extraction).\n",
+ "\n",
+ "The post-PEX netlist can be used for simulating a circuit. The simulation and performance evaluation of multiple design variations can be parallelized on a cloud platform for fast design space exploration. Fig. 1 describes the GLayout workflow.\n",
+ "\n",
+ "![workflow](https://i.imgur.com/BA7gY81.png)\n",
+ "\n",
+ "(Fig. 1: GLayout Workflow)\n",
+ "\n",
+ "### Generators\n",
+ "Generators in GLayout are Python functions that generate the layout and SPICE netlist for a circuit component. This allows for describing hierarchical and parameterized circuits, or PCells (Parameterized Cells), in Python.\n",
+ "\n",
+ "A generator can be a utility generator such as a Via, a primitive PCell such as a MOSFET, or a complex circuit as an Op-Amp.\n",
+ "\n",
+ "Generators are PDK-agnostic and hierarchical, and may call other generators. This allows complex components to be composed of simpler components hierarchically. Fig. 2 shows the hierarchical usage of generators in an example Op-Amp design, and Fig. 3 shows the creation of a high-level PCell from a primitive PCell.\n",
+ "\n",
+ "The SPICE netlist for a component is also generated hierarchically along with the layout.\n",
+ "\n",
+ "![hierarchy](https://i.imgur.com/YC4CXrp.png)\n",
+ "\n",
+ "(Fig. 2: Hierarchy of PCells in the Example Op-Amp Design)\n",
+ "\n",
+ "![high level pcell construction](https://i.imgur.com/KSgSHla.png)\n",
+ "\n",
+ "(Fig. 3: Creation of High-Level PCells from Primitive PCells)\n",
+ "\n",
+ "#### List of Generators\n",
+ "##### Utility Generators\n",
+ "- Via\n",
+ "- Guardring\n",
+ "- Routing (Straight, L, and C)\n",
+ "\n",
+ "##### PCell Generators\n",
+ "- Primitive Cells\n",
+ " - FET (NMOS, PMOS)\n",
+ " - MIM Capacitor\n",
+ "- Intermediate PCells\n",
+ " - Differential Pair\n",
+ " - Current Mirror\n",
+ " - Differential to Single Ended Converter\n",
+ "\n",
+ "##### Example Designs\n",
+ "- Two Stage Operational Amplifier"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "j4dNshkgMM4I"
+ },
+ "source": [
+ "# Using GLayout\n",
+ "### 1. Clone the repository and install dependencies\n",
+ "#### Python Dependencies\n",
+ "* [`gdsfactory`](https://github.com/gdsfactory/gdsfactory): Provides the backend for GDS manipulation.\n",
+ "* [`sky130`](https://github.com/gdsfactory/skywater130): The Skywater 130nm PDK Python package for GDSFactory to use in this demo.\n",
+ "* [`gf180`](https://github.com/gdsfactory/gf180): The GF 180nm PDK Python package for GDSFactory to use in this demo.\n",
+ "* [`gdstk`](https://heitzmann.github.io/gdstk/): (installed as a part of gdsfactory) Used for converting GDS files into SVG images for viewing.\n",
+ "* [`svgutils`](https://svgutils.readthedocs.io/en/latest/): To scale the SVG image.\n",
+ "\n",
+ "#### System Dependencies\n",
+ "* [`klayout`](https://klayout.de/): For DRC (Design Rule Checking).\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "JzDjayJIMSHe"
+ },
+ "outputs": [],
+ "source": [
+ "# Clone OpenFASoC\n",
+ "!git clone https://github.com/idea-fasoc/OpenFASOC\n",
+ "# Install python dependencies\n",
+ "!pip install sky130\n",
+ "!pip install gf180 prettyprinttree svgutils\n",
+ "!pip install gdsfactory==7.7.0\n",
+ "\n",
+ "import pathlib\n",
+ "import os\n",
+ "# Install KLayout (via conda)\n",
+ "!curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba\n",
+ "conda_prefix_path = pathlib.Path('conda-env')\n",
+ "CONDA_PREFIX = str(conda_prefix_path.resolve())\n",
+ "%env CONDA_PREFIX={CONDA_PREFIX}\n",
+ "\n",
+ "!bin/micromamba create --yes --prefix $CONDA_PREFIX\n",
+ "# Install from the litex-hub channel\n",
+ "!bin/micromamba install --yes --prefix $CONDA_PREFIX \\\n",
+ " --channel litex-hub \\\n",
+ " --channel main \\\n",
+ " klayout\n",
+ "\n",
+ "# Add conda packages to the PATH\n",
+ "PATH = os.environ['PATH']\n",
+ "%env PATH={PATH}:{CONDA_PREFIX}/bin\n",
+ "\n",
+ "%cd /content/OpenFASOC/openfasoc/generators/gdsfactory-gen"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ozTFXBekORtd"
+ },
+ "source": [
+ "### 2. Basic Usage of the GLayout Framework\n",
+ "Each generator is a Python function that takes a `MappedPDK` object as a parameter and generates a DRC clean layout for the given PDK. The generator may also accept a set of optional layout parameters such as the width or length of a MOSFET. All parameters are normal Python function arguments.\n",
+ "\n",
+ "The generator returns a `GDSFactory.Component` object that can be written to a `.gds` file and viewed using a tool such as Klayout. In this example, the `gdstk` library is used to convert the `.gds` file to an SVG image for viewing.\n",
+ "\n",
+ "The pre-PEX SPICE netlist for the component can be viewed using `component.info['netlist'].generate_netlist()`.\n",
+ "\n",
+ "In the following example the FET generator `glayout.primitives.fet` is imported and run with both the [Skywater 130](https://skywater-pdk.readthedocs.io/en/main/) and [GF180](https://gf180mcu-pdk.readthedocs.io/en/latest/) PDKs."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "MRjvYFZl6o8z"
+ },
+ "source": [
+ "#### Demonstration of Basic Layout / Netlist Generation in SKY130 & GF180"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "H0xylxwHOeKy"
+ },
+ "outputs": [],
+ "source": [
+ "from glayout.primitives.fet import nmos\n",
+ "from glayout.pdk.sky130_mapped import sky130_mapped_pdk as sky130\n",
+ "from glayout.pdk.gf180_mapped import gf180_mapped_pdk as gf180\n",
+ "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)\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.5)\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",
+ "metadata": {
+ "id": "YEaPdbyc-rh2"
+ },
+ "source": [
+ "#### Interactive Primitive Generation in SKY130\n",
+ "The following cell demonstrates the different PCell and Utility generators on the Sky130 PDK."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "DO_cMVFz-mHo"
+ },
+ "outputs": [],
+ "source": [
+ "from glayout.primitives import fet, mimcap, guardring\n",
+ "from glayout.components import diff_pair\n",
+ "import ipywidgets as widgets\n",
+ "\n",
+ "selection_button = widgets.RadioButtons(\n",
+ " options=['NMOS', 'PMOS', 'MIM Capacitor', 'Differential Pair', 'Guardring'],\n",
+ " orientation='horizontal',\n",
+ " description='Generator:',\n",
+ " layout=widgets.Layout(position='right')\n",
+ ")\n",
+ "generate_button = widgets.Button(description='Generate', disabled=False)\n",
+ "output = widgets.Output(layout = widgets.Layout(position='left', overflow='visible'))\n",
+ "hide = widgets.Output()\n",
+ "\n",
+ "grid = widgets.GridspecLayout(1, 2)\n",
+ "grid[0, 0] = widgets.VBox([selection_button, generate_button])\n",
+ "grid[0, 1] = output\n",
+ "\n",
+ "display(grid)\n",
+ "\n",
+ "with hide:\n",
+ " component = fet.nmos(pdk = sky130)\n",
+ "with output:\n",
+ " print('NMOS')\n",
+ " display_component(component)\n",
+ "\n",
+ "def generate_component(_):\n",
+ " selected_comp = selection_button.value\n",
+ "\n",
+ " with output:\n",
+ " clear_output()\n",
+ " print(f\"Generating {selected_comp}...\")\n",
+ " with hide:\n",
+ " match selected_comp:\n",
+ " case 'NMOS':\n",
+ " component = fet.nmos(pdk = sky130)\n",
+ " case 'PMOS':\n",
+ " component = fet.pmos(pdk = sky130)\n",
+ " case 'MIM Capacitor':\n",
+ " component = mimcap.mimcap(pdk = sky130)\n",
+ " case 'Differential Pair':\n",
+ " component = diff_pair.diff_pair(pdk = sky130)\n",
+ " case 'Guardring':\n",
+ " component = guardring.tapring(pdk = sky130)\n",
+ " with output:\n",
+ " clear_output()\n",
+ " print(selected_comp)\n",
+ " display_component(component, 3)\n",
+ "\n",
+ "generate_button.on_click(generate_component)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "gI9JNwN_fdL1"
+ },
+ "source": [
+ "### 3. Tweak the Parameters\n",
+ "These are some of the parameters the NMOS FET generator accepts:\n",
+ "* `width`: The gate width of the FET.\n",
+ "* `length`: The gate length of the FET.\n",
+ "* `fingers`: The number of fingers. Each finger shares the same source/drain.\n",
+ "* `multipliers`: Number of multipliers (a multiplier is a row of fingers).\n",
+ "\n",
+ "Run the below cell and use the sliders to adjust the parameters."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "-HIs4q1fC-vY"
+ },
+ "outputs": [],
+ "source": [
+ "# Default Values\n",
+ "width=3\n",
+ "length=0.2\n",
+ "fingers=4\n",
+ "multipliers=1\n",
+ "\n",
+ "# Create sliders\n",
+ "width_slider = widgets.FloatSlider(description = 'Width:', min = 1, max = 5, step = 0.5, value = width)\n",
+ "length_slider = widgets.FloatSlider(description = 'Length:', min = 0.2, max = 1, step = 0.1, value = length)\n",
+ "fingers_slider = widgets.IntSlider(description = 'Fingers:', min = 1, max = 10, value = fingers)\n",
+ "multipliers_slider = widgets.IntSlider(description = 'Multipliers:', min = 1, max = 5, value = multipliers)\n",
+ "generate_button = widgets.Button(description='Generate', disabled=False)\n",
+ "\n",
+ "inputs_box = widgets.VBox([width_slider, length_slider, fingers_slider, multipliers_slider, generate_button])\n",
+ "\n",
+ "output = widgets.Output(layout = widgets.Layout(position='left', overflow='visible'))\n",
+ "hide = widgets.Output()\n",
+ "\n",
+ "grid = widgets.GridspecLayout(1, 2)\n",
+ "grid[0, 0] = inputs_box\n",
+ "grid[0, 1] = output\n",
+ "\n",
+ "display(grid)\n",
+ "\n",
+ "def generate_component(_):\n",
+ " width = width_slider.value\n",
+ " length = length_slider.value\n",
+ " fingers = fingers_slider.value\n",
+ " multipliers = multipliers_slider.value\n",
+ "\n",
+ " with output:\n",
+ " clear_output()\n",
+ " print(f\"Generating with width={width}, length={length}, fingers={fingers}, multipliers={multipliers}...\")\n",
+ " with hide:\n",
+ " component = component = fet.nmos(pdk = sky130, width = width, length=length, fingers = fingers, multipliers = multipliers)\n",
+ " with output:\n",
+ " clear_output()\n",
+ " print(f\"N-MOSFET with width={width}, length={length}, fingers={fingers}, multipliers={multipliers}:\")\n",
+ " display_component(component)\n",
+ "\n",
+ "generate_component(None)\n",
+ "\n",
+ "# Regenerate upon change in value\n",
+ "generate_button.on_click(generate_component)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "g45S96g7BOhr"
+ },
+ "source": [
+ "### 4. DRC Checking Using External Tools (KLayout)\n",
+ "Design Rule Check (DRC) is the process of ensuring that a particular layout does not violate the constraints or _design rules_ imposed by the PDK.\n",
+ "\n",
+ "[Klayout](https://klayout.de/) is a GDSII viewer and editor that also has a DRC feature. The design rules for the PDK, in this case the Skywater 130 PDK, are described in a `.lydrc` file.\n",
+ "\n",
+ "The following cell runs DRC on the component generated in the previous cell. The number of DRC errors reported will be displayed at the end of the output."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "FSi37vB3GyvR"
+ },
+ "outputs": [],
+ "source": [
+ "!klayout out.gds -zz -r glayout/pdk/sky130_mapped/sky130.lydrc\n",
+ "!echo -e \"\\n$(grep -c \"\" sky130_drc.txt) DRC Errors Found\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Qw7Hsyft_NHm"
+ },
+ "source": [
+ "# Complex Circuit Example: Op-Amp\n",
+ "Using the above generators, complex circuit designs can be created by connecting the components. The function for creating such a design would itself be a generator. For example, differential pair generator uses the FET, Via, and routing generators.\n",
+ "\n",
+ "### Design\n",
+ "One such example circuit is the [Operational Amplifier](https://en.wikipedia.org/wiki/Operational_amplifier) (Op-Amp) defined in the `opamp.py` file. This design consists of a differential pair (input stage), a differential to single-ended converter (load), a common source (CS) gain stage, and an output buffer (for testing, it's not a part of the feedback loop), with an improved split-stage feedback created using a capacitor. The differential pair and the gain and output stages are biased using current mirrors.\n",
+ "\n",
+ "Each of the stages, the feedback capacitor, and the biasing circuitry were generated using the exported generators. See the schematic in Fig. 4 for an overview of the circuit. The PCells used (Differential Pair, Current Mirror, etc.) are highlighted with the dotted border.\n",
+ "\n",
+ "In Fig. 5(a), a Skywater 130nm layout for the Op-Amp is shown with the different components annotated. The annotated components are marked in the circuit schematic in Fig. 5(b) for the first two stages of the Op-Amp.\n",
+ "\n",
+ "![schematic](https://i.imgur.com/PUEPdXE.png)\n",
+ "\n",
+ "(Fig. 4: Example Op-Amp Circuit Schematic)\n",
+ "\n",
+ "![schemlayout](https://i.imgur.com/W2askiz.png)\n",
+ "\n",
+ "(Fig. 5: (a) Sky130 Op-Amp Layout and (b) the Corresponding Circuit Schematic for the First Two Stages of the Op-Amp)\n",
+ "\n",
+ "### Parameters\n",
+ "The Op-Amp generator accepts the following optional arguments:\n",
+ "- `half_diffpair_params`: A tuple of (width, length, fingers) for the differential pair. (3 values)\n",
+ "- `diffpair_bias`: A tuple of (width, length, fingers) for the differential pair bias transistors. (3)\n",
+ "- `half_common_source_params`: A tuple of (width, length, fingers, multipliers) for the common source PMOS transistor. (4)\n",
+ "- `half_common_source_bias`: A tuple of (width, length, fingers, multipliers) for the common source bias transistors. The `multipliers` only apply to the mirror transistor, reference transistor has a multiplier of 1. (4)\n",
+ "- `output_stage_params`: A tuple of (width, length, fingers) for the output stage NMOS transistor. (3)\n",
+ "- `output_stage_bias`: A tuple of (width, length, fingers) for the output stage bias transistors. (3)\n",
+ "- `half_pload`: A tuple of (width, length, fingers) for the load (differential to single-ended converter). The `fingers` only apply to the bottom two transistors. (3)\n",
+ "- `mim_cap_size`: A tuple of (width, length) for individual MIM capacitors. (2)\n",
+ "- `mim_cap_rows`: The number of rows in the MIM capacitor array (columns are fixed at 2). (1)\n",
+ "- `rmult`: The multiplier for the width of the routes. (1)\n",
+ "\n",
+ "The above arguments account for a total of 27 individual parameter values. These parameters can be changed to generate a very wide range of Op-Amp designs."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "e896CP65iqEY"
+ },
+ "source": [
+ "### 1. Generating the Op-Amp\n",
+ "The cell below generates the Op-Amp with a particular set of parameters and a PDK (Sky130 by default). Change any of the parameters or the PDK set at the beginning of the cell to generate different variations of the Op-Amp."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "qcbCRjQh_c9g"
+ },
+ "outputs": [],
+ "source": [
+ "from glayout.components.opamp import opamp\n",
+ "\n",
+ "# Select which PDK to use\n",
+ "pdk = sky130\n",
+ "# pdk = gf180\n",
+ "\n",
+ "# Op-Amp Parameters\n",
+ "half_diffpair_params = (6, 1, 4)\n",
+ "diffpair_bias = (6, 2, 4)\n",
+ "half_common_source_params = (7, 1, 10, 3)\n",
+ "half_common_source_bias = (6, 2, 8, 2)\n",
+ "output_stage_params = (5, 1, 16)\n",
+ "output_stage_bias = (6, 2, 4)\n",
+ "half_pload = (6, 1, 6)\n",
+ "mim_cap_size = (12, 12)\n",
+ "mim_cap_rows = 3\n",
+ "rmult = 2\n",
+ "\n",
+ "hide = widgets.Output()\n",
+ "\n",
+ "# Generate the Op-Amp\n",
+ "print('Generating Op-Amp...')\n",
+ "with hide:\n",
+ " component = opamp(pdk, half_diffpair_params, diffpair_bias, half_common_source_params, half_common_source_bias, output_stage_params, output_stage_bias, half_pload, mim_cap_size, mim_cap_rows, rmult)\n",
+ "\n",
+ "# Display the Op-Amp\n",
+ "clear_output()\n",
+ "display_component(component, 0.5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ayxczg2_lZdH"
+ },
+ "source": [
+ "### 2. Sweeping Variations\n",
+ "The [`sky130_nist_tapeout.py`](https://github.com/idea-fasoc/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130_nist_tapeout.py) file contains utilities for generating a matrix of Op-Amps with different parameters in the Sky130 PDK, running simulations, and generating statistics. This Python file is documented [here](https://github.com/idea-fasoc/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/README.md).\n",
+ "\n",
+ "In the cell below, an array of different Op-Amp parameters will be generated and a matrix of all the different variations will be created and displayed. Probe pads and \"Nanofab\" micropads are added for layout (See Fig. 6).\n",
+ "\n",
+ "The `get_small_parameter_list()` function is used to generate a small list of parameters. The function varies 7 out of the 27 parameters possible. A value is picked from a list for each of the 7 parameters while keeping the other values constant and returns a list of all possible combinations (1700+).\n",
+ "\n",
+ "The `create_opamp_matrix()` function generates Op-Amps from a given list of parameter values and appends them to a single GDS file for display. The function also adds \"pads\" to the Op-Amp that are used for probing on the physical layout. (See Fig. 6)\n",
+ "\n",
+ "![pads](https://i.imgur.com/5YIsYSY.png)\n",
+ "\n",
+ "(Fig 6. Pads Added to the Op-Amps in the Matrix)\n",
+ "\n",
+ "In test mode (default), only the first 2 Op-Amp varations will be used. Set the `TEST_MODE` variable to `False` to use all the generated variations. NOTE: This may take a very long time to run."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "1RlGEAkmyRkL"
+ },
+ "outputs": [],
+ "source": [
+ "%cd /content/OpenFASOC/openfasoc/generators/gdsfactory-gen/tapeout_and_RL\n",
+ "from sky130_nist_tapeout import *\n",
+ "\n",
+ "# Test mode. Set to False to generate 1700+ variations.\n",
+ "TEST_MODE = True\n",
+ "TEST_NUM_VARIANTS = 2 # These many variants will be generated if TEST_MODE = True\n",
+ "\n",
+ "# Generate parameter list\n",
+ "parameter_list = get_small_parameter_list()\n",
+ "\n",
+ "# Generate the Op-Amp matrix\n",
+ "create_opamp_matrix(save_dir_name = '.', params = parameter_list, indices = [i for i in range(TEST_NUM_VARIANTS)] if TEST_MODE else None)\n",
+ "\n",
+ "# Display the Op-Amp matrix\n",
+ "display_gds('opamp_matrix.gds', 0.35)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "AMuWdisV86Qc"
+ },
+ "source": [
+ "### 3. PEX and Spice Simulation\n",
+ "**Parasitic Extraction** (PEX) is the process of calculating the parasitic capacitive, resistive, and inductive effects of an electronic circuit layout to create an accurate analog model for simulation. In this Op-Amp example, parasitic extraction is done using the open-source tool [Magic](http://opencircuitdesign.com/magic/) and a SPICE netlist is generated for simulation using [this](https://github.com/idea-fasoc/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/extract.bash.template) script.\n",
+ "\n",
+ "[This](https://github.com/idea-fasoc/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/opamp_perf_eval.sp) SPICE testbench is used to run simulations on the post-PEX Op-Amp netlist. The testbench calculates the DC Gain, Unity Gain Bandwidth (UGB), Phase Margin, and 3dB Bandwidth at different values of the bias currents within a range. The best values of these metrics and the values of bias current at which they are achieved are written to `result_ac.txt`. The average total power consumption, and the estimated power consumption for the first two stages of the Op-Amp is written to `result_power.txt`.\n",
+ "\n",
+ "The `brute_force_full_layout_and_PEXsim()` function generates the full layouts for a given list of parameters, runs the post-PEX simulations, and returns an array with the simulation results corresponding to each set of parameters.\n",
+ "\n",
+ "In test mode, only the first 8 sets of parameters are simulated. Set `TEST_MODE` to `False` to run simulations on the whole range. NOTE: This may take a very long time to run."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "wKPMRJ4i9qoY"
+ },
+ "outputs": [],
+ "source": [
+ "# Test mode. Set to False to simulate the whole 1700+ variations.\n",
+ "TEST_MODE = True\n",
+ "\n",
+ "# Define a set of parameters to test\n",
+ "params_array = parameter_list[:8] if TEST_MODE else parameter_list\n",
+ "\n",
+ "# Run the simulations and get the results\n",
+ "!rm -r save_gds_by_index # cleans up any previous simulations\n",
+ "results_array = brute_force_full_layout_and_PEXsim(sky130, params_array)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "LxmqcEdFr7_M"
+ },
+ "source": [
+ "### 4. Finding The Best Op-Amp (Shotgun Approach)\n",
+ "The below cell generates a DC Gain v/s Unity Gain Bandwidth (UGB) plot from the results generated above.\n",
+ "\n",
+ "Based on these results, the best variation of the Op-Amp can be chosen for a given user specification. Specifications such as the highest gain or the least power consumption can be targeted."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Pgv94uUgsGI4"
+ },
+ "outputs": [],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "import matplotlib as mpl\n",
+ "from matplotlib import colormaps as cm\n",
+ "cmap = cm.get_cmap('jet')\n",
+ "\n",
+ "try:\n",
+ " params_list_of_dict = [{**opamp_parameters_de_serializer(opparam),**{\"index\":i}} for i,opparam in enumerate(params_array)]\n",
+ " results_list_of_dict = [{**opamp_results_de_serializer(opresult),**{\"index\":i}} for i,opresult in enumerate(results_array)]\n",
+ "except:\n",
+ " params_list_of_dict = [{**opamp_parameters_de_serializer_old(opparam),**{\"index\":i}} for i,opparam in enumerate(params_array)]\n",
+ " results_list_of_dict = [{**opamp_results_de_serializer_old(opresult),**{\"index\":i}} for i,opresult in enumerate(results_array)]\n",
+ "\n",
+ "# ilist is the list of output stage current bias (all of them are the same=93.5uA)\n",
+ "ugblist = np.array([opresult[\"ugb\"] for opresult in results_list_of_dict])\n",
+ "gainlist = np.array([opresult[\"dcGain\"] for opresult in results_list_of_dict])\n",
+ "powerlist = np.array([opresult[\"power\"] for opresult in results_list_of_dict])\n",
+ "freqlist = ugblist/10**(gainlist/20)\n",
+ "\n",
+ "fig, ax = plt.subplots(figsize = (10, 8))\n",
+ "colorlist = []\n",
+ "cnorm = mpl.colors.LogNorm(vmin=10e-5,vmax=1e-3)\n",
+ "sm = mpl.cm.ScalarMappable(cmap=cmap, norm=cnorm)\n",
+ "\n",
+ "for i in powerlist:\n",
+ " colorlist.append(cmap(cnorm(i)))\n",
+ "ax.scatter(freqlist,gainlist,s=1,alpha=1,c=colorlist)\n",
+ "plt.xlabel('Frequency ugb / Hz')\n",
+ "plt.ylabel('DC Gain / dB20')\n",
+ "ticks=[0.1e-6,1e-6,10e-6,100e-6]\n",
+ "aspect=60\n",
+ "cbar = fig.colorbar(sm, orientation='vertical', ax=ax, label='Power')\n",
+ "ax.set_xscale('log')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "XB50uQ7DMQbo"
+ },
+ "source": [
+ "## Reinforcement Learning Optimization\n",
+ "Reinforcement Learning (RL) is a field of machine learning and optimal control about finding the most effective behavior of an agent in a dynamic environment to maximize rewards. The focus in RL is finding a balance between exploration and exploitation. The agent learns to perturb the state of the environment to derive maximum reward.\n",
+ "\n",
+ "### RL Optimization in GLayout\n",
+ "Here, an RL framework is used to derive the most optimal parameters for a GLayout circuit to maximize the reward derived from the results of the simulation testbench. At each step, the RL agent decides an action (increase/decrease) for each of the parameters based on the specifications observed from the simulation environment. A new set of parameters are generated and fed to the simulation environment, completing the loop. Fig. 6 shows an overview of the framework.\n",
+ "\n",
+ "Each specification is a set of minimum DC Gain and a figure of merit (FoM). The FoM, \"Unity Gain Bandwidth (UGB) efficiency\", is defined as the ratio of UGB to the average power consumption ($FoM=UGB/power$).\n",
+ "\n",
+ "In this process, two types of specifications are used: Optimized Specifications (oS), with the aim of maximizing their growth, and Capped Specifications (cS), with the aim of reaching the target and stop. As illustrated in Fig. 6, the *cS* reach the target specification, and from that point forward, the *oS* continues to grow as much as possible.\n",
+ "\n",
+ "![rl-framework](https://i.imgur.com/uHdfs44.png)\n",
+ "\n",
+ "(Fig. 6: Overview of the RL Framework)\n",
+ "\n",
+ "The reward, $r$, is calculated as follows:\n",
+ "\n",
+ "$r_{cS} = \\mathrm{min}(\\frac{cS-cS*}{cS+cS*}, 0), \\hspace{0.5cm} r_{oS} = (\\frac{oS-oS*}{oS+oS*})$\n",
+ "\n",
+ "$r = \\sum r_{cS} + \\sum r_{oS}$\n",
+ "\n",
+ "The example in the following cells demonstrates the training and validation of an RL model for the Op-Amp generated above."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "BDd0SUocSgqt"
+ },
+ "source": [
+ "### 1. Install Dependencies\n",
+ "The following libraries are used for the RL optimization:\n",
+ "1. [OpenAI Gym](https://github.com/openai/gym)\n",
+ "2. [Ray](https://github.com/ray-project/ray)\n",
+ "3. [PyTorch](https://github.com/pytorch/pytorch)\n",
+ "\n",
+ "The following cell install all of these libraries and their dependencies."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "XDWTMHBIuVmu"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install gym gymnasium ray[tune] torch dm_tree lz4"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "vVz4FJDMbr0x"
+ },
+ "source": [
+ "### 2. Train the Model\n",
+ "During training, the _hyperparameters_ (the parameters that define the behaviour of the agent) are tuned. We generate 100 different target specifications within a reasonable range for the training process, to comprehensively cover various regions of the design space.\n",
+ "\n",
+ "The specifications are generated using the `generate_random_specs()` which generates a random set of 100 specifications in a given range. These specifications are saved to `train.yaml`, which is read by `train_model()`. The trained model checkpoint is saved to the `./checkpoint_save` directory.\n",
+ "\n",
+ "NOTE: This will take a very long time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "XLPouR9Xuxbe"
+ },
+ "outputs": [],
+ "source": [
+ "%cd /content/OpenFASOC/openfasoc/MLoptimization\n",
+ "\n",
+ "from gen_spec import generate_random_specs\n",
+ "\n",
+ "# Generate 100 specifications in train.yaml\n",
+ "generate_random_specs(\"train.yaml\", int(100))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "XKT_NV3RvUo7"
+ },
+ "outputs": [],
+ "source": [
+ "from model import train_model\n",
+ "\n",
+ "# Train the model and save the checkpoint to ./checkpoint_save\n",
+ "train_model('./checkpoint_save')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ppCtAiQcbxOq"
+ },
+ "source": [
+ "### 3. Validation\n",
+ "Given a specification to achieve, the trained model will iterate until the best set of parameters to achieve that specification are found. To validate the training, we generate another set of 100 random specifications. The `evaluate_model()` loads the previously trained checkpoint `./checkpoint_save`, and reports how many of these specifications are achieved with the parameters generated by the trained model within a finite number of iterations.\n",
+ "\n",
+ "An ideal model would be able to achieve all of the generated specifications and within the least possible number of steps. The output of the cell below reports the number of specs tested and the number of specs achieved, as well as logs the reward and action taken at each iteration.\n",
+ "\n",
+ "NOTE: This will take a very long time. This time can be reduced if it is run in parallel."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "E6CmJXkIv2D7"
+ },
+ "outputs": [],
+ "source": [
+ "from eval import evaluate_model\n",
+ "\n",
+ "generate_random_specs(\"train.yaml\", int(100))\n",
+ "evaluate_model('./checkpoint_save')"
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3.9.13 64-bit",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.9.13"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "397704579725e15f5c7cb49fe5f0341eb7531c82d19f2c29d197e8b64ab5776b"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/extract.bash.template b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/extract.bash.template
index 54f50aeba..309d60cb6 100644
--- a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/extract.bash.template
+++ b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/extract.bash.template
@@ -9,12 +9,23 @@ export PDK_ROOT=@@PDK_ROOT
magic -rcfile ./sky130A/sky130A.magicrc -noconsole -dnull << EOF
gds read $1
+flatten $2
load $2
+select top cell
+extract do local
extract all
-ext2spice merge aggressive
+ext2sim labels on
+ext2sim
+extresist tolerance 10
+extresist
+ext2spice lvs
+ext2spice cthresh 0
+ext2spice extresist on
ext2spice -o $2_pex.spice
exit
EOF
-
+rm -f $2.nodes
rm -f $2.ext
+rm -f $2.res.ext
+rm -f $2.sim