2024-10-29 10:55:48 +01:00
{
"cells": [
2024-11-12 09:46:24 +01:00
{
"cell_type": "markdown",
"id": "96c73c5c-d26f-4710-9dce-8b67c625438e",
"metadata": {},
"source": [
"# Simple setup for using gm/id with IHP open PDK"
]
},
2024-10-29 10:55:48 +01:00
{
"cell_type": "code",
2024-11-12 09:46:24 +01:00
"execution_count": 4,
"id": "93924bd4-4ba6-4275-8643-a10c59a209e7",
2024-10-29 10:55:48 +01:00
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
2024-11-12 09:46:24 +01:00
"#for windows\n",
"#import sys\n",
"#sys.path.append(r'C:\\Users\\....\\gmid') # path to gmid repository\n",
"# ------\n",
2024-10-29 10:55:48 +01:00
"import matplotlib.pyplot as plt\n",
2024-11-12 09:46:24 +01:00
"from mosplot import load_lookup_table, LoadMosfet # make sure that mosplot can be found in the python path\n",
"import ipywidgets as widgets\n",
"from ipywidgets import interactive\n",
"from ipywidgets import interactive_output, HBox, VBox\n",
"import matplotlib.ticker as ticker "
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "code",
2024-11-12 09:46:24 +01:00
"execution_count": 5,
"id": "16b9f998-3107-436e-842d-c588dc4cbfee",
2024-10-29 10:55:48 +01:00
"metadata": {},
"outputs": [],
"source": [
2024-11-12 09:46:24 +01:00
"pmos_lv_path = '/home/pedersen/projects/IHP-AnalogAcademy/modules/module_0_foundations/gmid_sweeps/pmos_lv_sweep.npy'\n",
"nmos_lv_path ='/home/pedersen/projects/IHP-AnalogAcademy/modules/module_0_foundations/gmid_sweeps/nmos_lv_sweep.npy'\n",
"\n",
"lookup_table_pmos = load_lookup_table(pmos_lv_path)\n",
"lookup_table_nmos = load_lookup_table(nmos_lv_path)"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "code",
2024-11-12 09:46:24 +01:00
"execution_count": 6,
"id": "1eae8452-b1d5-4611-bb86-4f2f222b2929",
2024-10-29 10:55:48 +01:00
"metadata": {},
"outputs": [],
"source": [
2024-11-12 09:46:24 +01:00
"nmos = LoadMosfet(lookup_table=lookup_table_nmos, mos=\"nmos\", vsb=0.0, vds=0.4)\n",
"pmos = LoadMosfet(lookup_table=lookup_table_pmos, mos=\"pmos\", vsb=0, vds=-0.6, vgs=(-1.2, -0.1))"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "markdown",
2024-11-12 09:46:24 +01:00
"id": "576b86e1-caae-496f-a644-584ff4fb5dd6",
2024-10-29 10:55:48 +01:00
"metadata": {},
"source": [
2024-11-12 09:46:24 +01:00
"# Function Definitions"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "code",
2024-11-12 09:46:24 +01:00
"execution_count": 7,
"id": "7abd7d3e-cf3d-4643-b5b3-d35f2362f7b7",
"metadata": {
"jupyter": {
"source_hidden": true
}
},
2024-10-29 10:55:48 +01:00
"outputs": [],
"source": [
2024-11-12 09:46:24 +01:00
"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",
" length_flat = np.array(length).flatten()\n",
"\n",
" # Ensure all inputs have the same length\n",
" if not (len(x_values_flat) == len(y_values_flat) == len(z_values_flat) == len(length_flat)):\n",
" raise ValueError(\"All input arrays (x_values, y_values, z_values, length) must have the same number of elements.\")\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=(8, 6)) # Ensure the plot is drawn fresh for each update\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.1\n",
" # Recalculate the mask with matching shapes\n",
" mask = np.abs(length_flat * 1e6 - selected_length_in_micro) < tolerance\n",
"\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='Select Length:',\n",
" )\n",
"\n",
" x_value_widget = widgets.FloatText(\n",
" value=np.mean(x_values_flat),\n",
" description=f\"Select {x_axis_name}:\",\n",
" disabled=False\n",
" )\n",
"\n",
" y_value_widget = widgets.FloatText(\n",
" value=None,\n",
" description=f\"Set {y_axis_name}:\",\n",
" disabled=True\n",
" )\n",
"\n",
" z_value_widget = widgets.FloatText(\n",
" value=None,\n",
" description=f\"Corresponding z value:\",\n",
" disabled=True\n",
" )\n",
"\n",
" select_x_or_y_widget = widgets.Checkbox(\n",
" value=True,\n",
" description=f\"Select {x_axis_name} (uncheck for {y_axis_name})\",\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",
"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"
2024-10-29 10:55:48 +01:00
]
},
{
2024-11-12 09:46:24 +01:00
"cell_type": "markdown",
"id": "57bc7b38-a0e8-40c3-90c5-bd7c2bf03df4",
2024-10-29 10:55:48 +01:00
"metadata": {},
"source": [
2024-11-12 09:46:24 +01:00
"# Plotting examples\n",
"\n",
"Note: In my cases i will be using my own defined functions to plot more interatively. You can refer to the standard repository to see how to plot using the native functions in the repo..."
2024-10-29 10:55:48 +01:00
]
},
{
2024-11-12 09:46:24 +01:00
"cell_type": "code",
"execution_count": 8,
"id": "cdfafca1-e7c0-4491-8a38-5785f38c8d60",
2024-10-29 10:55:48 +01:00
"metadata": {},
2024-11-12 09:46:24 +01:00
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "b02c4d3ead7440c2803417b6fcc005cf",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "03c6dd08648e483ea151c95baae95aa4",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
2024-10-29 10:55:48 +01:00
"source": [
2024-11-12 09:46:24 +01:00
"# Lets start by plotting the intrinsic gain of both pmos and nmos as a function of gm/id. We start by setting the data arrays\n",
"\n",
"id_values_nmos = nmos.extracted_table['id']\n",
"gm_values_nmos = nmos.extracted_table['gm']\n",
"gds_values_nmos = nmos.extracted_table['gds']\n",
"vgs_values_nmos = nmos.extracted_table['vgs']\n",
"\n",
"id_values_pmos = pmos.extracted_table['id']\n",
"gm_values_pmos = pmos.extracted_table['gm']\n",
"gds_values_pmos = pmos.extracted_table['gds']\n",
"vgs_values_pmos = pmos.extracted_table['vgs']\n",
"\n",
"\n",
"plot_data_vs_data(gm_values_nmos/id_values_nmos, gm_values_nmos/gds_values_nmos, vgs_values_nmos, nmos.extracted_table['lengths'], 'gm/id', 'gds') # plotting nmos data\n",
"plot_data_vs_data(gm_values_pmos/id_values_pmos, gm_values_pmos/gds_values_pmos, vgs_values_pmos, pmos.extracted_table['lengths'], 'gm/id', 'gds') # plotting pmos data"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "markdown",
2024-11-12 09:46:24 +01:00
"id": "1cd1cfe6-8260-4b9f-8166-f953758c0563",
2024-10-29 10:55:48 +01:00
"metadata": {},
"source": [
2024-11-12 09:46:24 +01:00
"# Verifying the models\n",
"To ensure that the gmid library is working properly we will simply set the dimensions for a nmos a varify in xschem"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "code",
2024-11-12 09:46:24 +01:00
"execution_count": 9,
"id": "7dd17dfa-abd2-4c76-9fd4-1f7824fdaa58",
2024-10-29 10:55:48 +01:00
"metadata": {},
"outputs": [
{
2024-11-12 09:46:24 +01:00
"name": "stdout",
"output_type": "stream",
"text": [
"1.0 μA\n"
]
2024-10-29 10:55:48 +01:00
}
],
"source": [
2024-11-12 09:46:24 +01:00
"gmid = 18 #moderate inversion\n",
"id = 1e-6\n",
"gm = gmid*id\n",
"display_id, unit_id = display_current(id)\n",
"print(f'{display_id} {unit_id}')"
]
},
{
"cell_type": "markdown",
"id": "c5edba7b-f8ec-4405-a7b3-908489a7c47f",
"metadata": {},
"source": [
"# Choosing the channel length by quick overview"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "markdown",
2024-11-12 09:46:24 +01:00
"id": "5a570be2-c30c-456e-ad10-f18870d1d27e",
2024-10-29 10:55:48 +01:00
"metadata": {},
"source": [
2024-11-12 09:46:24 +01:00
"Here the focus is on getting a high intrinsic gain"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "code",
2024-11-12 09:46:24 +01:00
"execution_count": 10,
"id": "7fd6ceee-3d40-460f-82d6-6d672d0d337d",
2024-10-29 10:55:48 +01:00
"metadata": {},
"outputs": [
{
"data": {
2024-11-12 09:46:24 +01:00
"application/vnd.jupyter.widget-view+json": {
"model_id": "82d7c57bbc6f44a79891a51bcd92c82e",
"version_major": 2,
"version_minor": 0
},
2024-10-29 10:55:48 +01:00
"text/plain": [
2024-11-12 09:46:24 +01:00
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
2024-10-29 10:55:48 +01:00
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
2024-11-12 09:46:24 +01:00
"plot_data_vs_data(gm_values_nmos/id_values_nmos, gm_values_nmos/gds_values_nmos, vgs_values_nmos, nmos.extracted_table['lengths'], 'gm/id', 'gds') # plotting nmos data"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "code",
2024-11-12 09:46:24 +01:00
"execution_count": 11,
"id": "3fa1a702-34dd-43de-a95e-a6654fea16eb",
2024-10-29 10:55:48 +01:00
"metadata": {},
2024-11-12 09:46:24 +01:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"R_load = 2.25 MΩ\n"
]
}
],
2024-10-29 10:55:48 +01:00
"source": [
2024-11-12 09:46:24 +01:00
"# By sweeping through the lenghts for a fixed gm/id we see that the lengths is approximatly 3.25e-6\n",
"Lnmos = 3.25e-6\n",
"gmro = 40.44\n",
2024-10-29 10:55:48 +01:00
"ro = gmro/gm\n",
2024-11-12 09:46:24 +01:00
"display_ro, unit_ro = display_resistance(ro)\n",
"print(f'R_load = {display_ro:.2f} {unit_ro}')"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "markdown",
2024-11-12 09:46:24 +01:00
"id": "20cf0ad2-022a-45d6-8a1f-7c41fd2e26c7",
2024-10-29 10:55:48 +01:00
"metadata": {},
"source": [
2024-11-12 09:46:24 +01:00
"# Now we want to find the Corresponding width"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "code",
2024-11-12 09:46:24 +01:00
"execution_count": 12,
"id": "eb54a54a-491b-4c94-8d34-d14457474b25",
2024-10-29 10:55:48 +01:00
"metadata": {},
"outputs": [
{
"data": {
2024-11-12 09:46:24 +01:00
"application/vnd.jupyter.widget-view+json": {
"model_id": "9056d3790ad7459bbf57e878d655cce1",
"version_major": 2,
"version_minor": 0
},
2024-10-29 10:55:48 +01:00
"text/plain": [
2024-11-12 09:46:24 +01:00
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
2024-10-29 10:55:48 +01:00
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
2024-11-12 09:46:24 +01:00
"width_nmos = nmos.extracted_table['width']\n",
"plot_data_vs_data(gm_values_nmos/id_values_nmos, id_values_nmos/width_nmos, vgs_values_nmos, nmos.extracted_table['lengths'], 'gm/id', 'id/W', log=True) # plotting nmos data"
2024-10-29 10:55:48 +01:00
]
},
{
"cell_type": "code",
2024-11-12 09:46:24 +01:00
"execution_count": 13,
"id": "e6e2b885-e999-4e29-9a07-d9183db4c021",
2024-10-29 10:55:48 +01:00
"metadata": {},
2024-11-12 09:46:24 +01:00
"outputs": [],
"source": [
"# We see that for a gm/id of 18 and length of 3.25 our, id/W is given as 0.3\n",
"id_over_width_nmos = 0.3\n",
"Wnmos = id/id_over_width_nmos"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "9ff5d012-44c7-4acb-b7e2-19ff15312594",
"metadata": {
"scrolled": true
},
2024-10-29 10:55:48 +01:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
2024-11-12 09:46:24 +01:00
"\n",
"Width and Length for NMOS\n",
" W = 3.33 um\n",
" L = 3.25 um\n",
"Inversion Region for NMOS: Moderate Inversion\n",
"\n",
"Bias Current:\n",
" Id = 1.00μA\n",
"Parameters to check\n",
" gm/gds = 40.44\n",
" gm = 0.018 mS\n",
" ro = 2.25MΩ\n",
"\n",
"\n"
2024-10-29 10:55:48 +01:00
]
}
],
"source": [
2024-11-12 09:46:24 +01:00
"# Now we can summarize everything\n",
"\n",
"single_transistor_summary = f\"\"\"\n",
"Width and Length for NMOS\n",
" W = {Wnmos*1e6:.2f} um\n",
" L = {Lnmos*1e6:.2f} um\n",
"Inversion Region for NMOS: {determine_inversion_region(gmid, 'nmos')}\n",
"\n",
"Bias Current:\n",
" Id = {display_id:.2f}{unit_id}\n",
"Parameters to check\n",
" gm/gds = {gmro:.2f}\n",
" gm = {gm*1e3:.2} mS\n",
" ro = {display_ro:.2f}{unit_ro}\n",
"\n",
"\"\"\"\n",
"print(single_transistor_summary)"
2024-10-29 10:55:48 +01:00
]
2024-11-12 09:46:24 +01:00
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a0c63c5-99f4-46ae-a827-bb7525950798",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "16b8b4f2-807a-4f75-8bc1-cf12a7d1ec0f",
"metadata": {},
"outputs": [],
"source": []
2024-10-29 10:55:48 +01:00
}
],
"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
}