{ "cells": [ { "cell_type": "code", "execution_count": 34, "id": "9f325daf-eb3b-4cf7-8ffe-b9b94e7f66ea", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from mosplot.plot import load_lookup_table, Mosfet, Expression\n", "import ipywidgets as widgets\n", "from ipywidgets import interactive\n", "from ipywidgets import interactive_output, HBox, VBox\n", "import matplotlib.ticker as ticker " ] }, { "cell_type": "code", "execution_count": 35, "id": "b5b31aca-47bf-4461-8e50-16c20f03b337", "metadata": {}, "outputs": [], "source": [ "lookup_table_nmos = load_lookup_table('../sg13_nmos_lv_LUT.npz')\n", "lookup_table_pmos = load_lookup_table('../sg13_pmos_lv_LUT.npz')" ] }, { "cell_type": "code", "execution_count": 36, "id": "743dc381-0d35-4aa9-847c-c42c80c17786", "metadata": {}, "outputs": [], "source": [ "nmos = Mosfet(lookup_table=lookup_table_nmos, mos=\"sg13_lv_nmos \", vbs=0.0, vds=0.6)\n", "pmos = Mosfet(lookup_table=lookup_table_pmos, mos=\"sg13_lv_pmos\", vbs=0.0, vds=-0.6, vgs=(-1.2, -0.15))" ] }, { "cell_type": "code", "execution_count": 37, "id": "b27d5fca-3436-4df7-895f-f6a4bbd7a80d", "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "def plot_data_vs_data(x_values, y_values, z_values, length, x_axis_name, y_axis_name='y', y_multiplier=1, log=False):\n", " x_values_flat = np.array(x_values).flatten()\n", " y_values_flat = np.array(y_values, dtype=np.float64).flatten()\n", " z_values_flat = np.array(z_values, dtype=np.float64).flatten()\n", " \n", " length_arr = np.array(length)\n", " \n", " length_flat = length_arr.flatten()\n", " \n", "\n", " unique_lengths = np.unique(length_flat)\n", " unique_lengths_in_micro = unique_lengths * 1e6\n", "\n", " def update_plot(selected_length, x_value=None, y_value=None):\n", " plt.figure(figsize=(12, 8)) # Make the figure wider (adjust as needed)\n", "\n", " if selected_length == \"Show All\":\n", " mask = np.ones_like(length_flat, dtype=bool)\n", " else:\n", " selected_length_in_micro = float(selected_length.replace(' μm', ''))\n", " tolerance = 0.01 # Tighten the tolerance to avoid unwanted data points\n", " mask = np.abs(length_flat * 1e6 - selected_length_in_micro) < tolerance\n", "\n", " # Apply the mask to the data\n", " x_values_for_length = x_values_flat[mask]\n", " y_values_for_length = y_values_flat[mask] * y_multiplier\n", " z_values_for_length = z_values_flat[mask]\n", " length_for_length = length_flat[mask] * 1e6\n", "\n", " if selected_length == \"Show All\":\n", " for length_value in np.unique(length_for_length):\n", " mask_all = (length_for_length == length_value)\n", " plt.plot(x_values_for_length[mask_all], y_values_for_length[mask_all])\n", "\n", " min_length = np.min(unique_lengths_in_micro)\n", " max_length = np.max(unique_lengths_in_micro)\n", " plt.title(f'{y_axis_name} vs {x_axis_name} (Length from {min_length:.2f} μm to {max_length:.2f} μm)')\n", "\n", " else:\n", " plt.plot(x_values_for_length, y_values_for_length)\n", " plt.title(f'{y_axis_name} vs {x_axis_name} for {selected_length}')\n", "\n", " plt.xlabel(f'{x_axis_name}')\n", " plt.ylabel(f'{y_axis_name}')\n", "\n", " if log:\n", " plt.yscale('log')\n", " plt.gca().yaxis.set_major_locator(ticker.LogLocator(base=10, subs=[], numticks=10))\n", " plt.gca().yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f'$10^{int(np.log10(x))}$'))\n", " plt.ylabel(f'{y_axis_name} (Log Base 10)')\n", "\n", " if y_value is not None and x_value_widget.disabled:\n", " closest_index = np.abs(y_values_for_length - y_value).argmin()\n", " closest_x = x_values_for_length[closest_index]\n", " closest_y = y_values_for_length[closest_index]\n", " corresponding_z = z_values_for_length[closest_index]\n", "\n", " plt.scatter(closest_x, closest_y, color='blue', label=f'Point ({closest_x:.2f}, {closest_y:.2f})')\n", " z_value_widget.value = corresponding_z\n", " print(f\"The corresponding {x_axis_name} value for {y_axis_name} = {closest_y:.2f} is: {closest_x:.2f}\")\n", " elif x_value is not None and y_value_widget.disabled:\n", " closest_index = np.abs(x_values_for_length - x_value).argmin()\n", " closest_x = x_values_for_length[closest_index]\n", " closest_y = y_values_for_length[closest_index]\n", " corresponding_z = z_values_for_length[closest_index]\n", "\n", " plt.scatter(closest_x, closest_y, color='red', label=f'Point ({closest_x:.2f}, {closest_y:.2f})')\n", " z_value_widget.value = corresponding_z\n", " print(f\"The corresponding {y_axis_name} value for {x_axis_name} = {closest_x:.2f} is: {closest_y:.2f}\")\n", "\n", " plt.grid(True)\n", " plt.legend()\n", " plt.show()\n", "\n", " dropdown_options = [\"Show All\"] + [f'{length:.2f} μm' for length in unique_lengths_in_micro]\n", " length_widget = widgets.Dropdown(\n", " options=dropdown_options,\n", " value=dropdown_options[0],\n", " description='Length:',\n", " layout=widgets.Layout(width='500px') # Make the dropdown wider\n", " )\n", "\n", " x_value_widget = widgets.FloatText(\n", " value=np.mean(x_values_flat),\n", " description=f\"{x_axis_name}:\",\n", " disabled=False,\n", " layout=widgets.Layout(width='300px', margin='0 40px 0 0'), # Push input boxes more to the right\n", " description_width='150px' # Smaller description width\n", " )\n", "\n", " y_value_widget = widgets.FloatText(\n", " value=None,\n", " description=f\"{y_axis_name}:\",\n", " disabled=True,\n", " layout=widgets.Layout(width='300px', margin='0 40px 0 0'), # Push input boxes more to the right\n", " description_width='150px' # Smaller description width\n", " )\n", "\n", " z_value_widget = widgets.FloatText(\n", " value=None,\n", " description=f\" Vgs:\",\n", " disabled=True,\n", " layout=widgets.Layout(width='300px', margin='0 40px 0 0'), # Push input boxes more to the right\n", " description_width='150px' # Smaller description width\n", " )\n", "\n", " select_x_or_y_widget = widgets.Checkbox(\n", " value=True,\n", " description=f\"{x_axis_name} (uncheck for {y_axis_name})\",\n", " layout=widgets.Layout(width='300px') # Make the checkbox wider\n", " )\n", "\n", " def toggle_x_or_y(change):\n", " if change['new']:\n", " x_value_widget.disabled = False\n", " y_value_widget.disabled = True\n", " else:\n", " x_value_widget.disabled = True\n", " y_value_widget.disabled = False\n", "\n", " select_x_or_y_widget.observe(toggle_x_or_y, names='value')\n", "\n", " output = interactive_output(update_plot, {\n", " 'selected_length': length_widget,\n", " 'x_value': x_value_widget,\n", " 'y_value': y_value_widget\n", " })\n", "\n", " display(VBox([length_widget, select_x_or_y_widget, HBox([x_value_widget, y_value_widget]), z_value_widget, output]))\n", "\n", "\n", "\n", "def tile_length_to_match_data(length_array, data_array):\n", " length_array = np.array(length_array).flatten() \n", " data_shape = data_array.shape \n", " \n", " if length_array.size == data_shape[0]:\n", " # length matches number of rows, repeat along columns\n", " return np.tile(length_array.reshape(-1, 1), (1, data_shape[1]))\n", " elif length_array.size == data_shape[1]:\n", " # length matches number of columns, repeat along rows\n", " return np.tile(length_array.reshape(1, -1), (data_shape[0], 1))\n", " else:\n", " raise ValueError(f\"Length array size {length_array.size} does not match any dimension of data shape {data_shape}\")\n", "\n", "\n", " \n", "def display_resistance(ro_value):\n", " \"\"\"Determine the resistance value and its unit.\"\"\"\n", " if ro_value < 1e3:\n", " return ro_value, \"Ω\"\n", " elif ro_value < 1e6:\n", " return ro_value / 1e3, \"kΩ\"\n", " elif ro_value < 1e9:\n", " return ro_value / 1e6, \"MΩ\"\n", " else:\n", " return ro_value / 1e9, \"GΩ\"\n", "\n", "def display_current(Id_value):\n", " \"\"\"Determine the current value and its unit.\"\"\"\n", " if Id_value < 1e-6:\n", " return Id_value * 1e9, \"nA\" # Convert to nA\n", " elif Id_value < 1e-3:\n", " return Id_value * 1e6, \"μA\" # Convert to μA\n", " else:\n", " return Id_value * 1e3, \"mA\" # Convert to mA\n", " \n", "def dB_to_linear(av_db):\n", " return 10 ** (av_db / 20)\n", "\n", "\n", "def determine_inversion_region(gm_id_value, device_type):\n", " \"\"\"Determine the inversion region based on gm/id value for NMOS or PMOS.\"\"\"\n", " if device_type == 'nmos':\n", " if gm_id_value > 20:\n", " return \"Weak Inversion\"\n", " elif 10 < gm_id_value <= 20:\n", " return \"Moderate Inversion\"\n", " else:\n", " return \"Strong Inversion\"\n", " elif device_type == 'pmos':\n", " if gm_id_value > 20:\n", " return \"Weak Inversion\"\n", " elif 10 < gm_id_value <= 20:\n", " return \"Moderate Inversion\"\n", " else:\n", " return \"Strong Inversion\"\n", " else:\n", " raise ValueError(\"Invalid device type. Use 'nmos' or 'pmos'.\")\n", " \n" ] }, { "cell_type": "code", "execution_count": 38, "id": "fc95a0ff-ba36-46ce-9fa1-64d6d2d7770f", "metadata": {}, "outputs": [], "source": [ "### ------ automated flattening based on shape ------### \n", "length_2d_pmos = tile_length_to_match_data(pmos.length, pmos.extracted_table['gm'])\n", "length_2d_nmos = tile_length_to_match_data(nmos.length, nmos.extracted_table['gm'])" ] }, { "cell_type": "markdown", "id": "2fc675aa-6d59-4d74-83e2-18c56353db0d", "metadata": {}, "source": [ "# NMOS GMID" ] }, { "cell_type": "code", "execution_count": 39, "id": "b7cc630f-b385-47a6-a6f9-ac0d10effffe", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "c0d716a395d84be68ee1dbc73a913175", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "width_values = nmos.width\n", "id_values = nmos.extracted_table['id']\n", "gm_values = nmos.extracted_table['gm']\n", "gds_values = nmos.extracted_table['gds']\n", "vgs_values= nmos.extracted_table['vgs']\n", "\n", "plot_data_vs_data(\n", " gm_values/id_values,\n", " gm_values/gds_values,\n", " vgs_values,\n", " length_2d_nmos,\n", " 'gm/id',\n", " 'gm/gds'\n", ")" ] }, { "cell_type": "markdown", "id": "e847c359-b57e-4e84-b0dc-93616d575efd", "metadata": {}, "source": [ "# PMOS GMID" ] }, { "cell_type": "code", "execution_count": 40, "id": "3727c42d-a4bf-4eb0-bc11-6e859ae41324", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "cba5a4c794944ea1900cd54144dad957", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "width_values = pmos.width\n", "id_values = pmos.extracted_table['id']\n", "gm_values = pmos.extracted_table['gm']\n", "gds_values = pmos.extracted_table['gds']\n", "vgs_values= pmos.extracted_table['vgs']\n", "\n", "plot_data_vs_data(gm_values/id_values, gm_values/gds_values, vgs_values, length_2d_pmos, 'gm/id', 'gm/gds')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "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.12" } }, "nbformat": 4, "nbformat_minor": 5 }