Compare commits

..

6 Commits

Author SHA1 Message Date
PhillipRambo be5255ede9 push new flatten logic 2025-07-17 15:08:44 +02:00
PhillipRambo 07d52ddd14 Merge branch 'main' into dev 2025-07-14 12:48:03 +02:00
PhillipRambo 5df59654e3
Merge pull request #47 from engrvip123/gmid_check
Replace hardcoded PDK paths with environment variable based paths in gmid .py scripts for NMOS and PMOS
2025-07-14 12:06:27 +02:00
PhillipRambo 8e1518b883
Update foundations.md 2025-07-14 11:59:43 +02:00
PhillipRambo d91421fe8f
Update foundations.md 2025-07-14 11:58:44 +02:00
engrvip123 f399a5e6e5 Replace hardcoded PDK paths with environment variable based paths for portability 2025-07-11 12:03:46 +05:30
5 changed files with 142 additions and 105 deletions

View File

@ -81,7 +81,7 @@ The scripts that was made for generating the LUTs can be found as:
### 🔧 Setup Procedure
1. **Clone the Metwatts GMID repository**
Follow the installation instructions provided in the repo.
Follow the installation instructions provided in the repo ([`gmid`](https://github.com/medwatt/gmid))
> 💡 It is recommended to use a virtual environment.
2. **Generate Lookup Tables**
@ -101,7 +101,7 @@ The scripts that was made for generating the LUTs can be found as:
```bash
jupyter lab _gmid_test.ipynb
```
This notebook will open a GUI for exploring the GMID lookup tables, which should look like the following:
This notebook will open a GUI for exploring the GMID lookup tables, which should look like the following (make sure the paths match yours accordingly):
<p align="center"> <img src=".media/gmid.png" width="800" height="400" /> </p>

View File

@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 34,
"id": "9f325daf-eb3b-4cf7-8ffe-b9b94e7f66ea",
"metadata": {},
"outputs": [],
@ -18,7 +18,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 35,
"id": "b5b31aca-47bf-4461-8e50-16c20f03b337",
"metadata": {},
"outputs": [],
@ -29,41 +29,18 @@
},
{
"cell_type": "code",
"execution_count": 4,
"id": "a03cd944-2432-457c-9b88-486ab781fde6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"dict_keys(['sg13_lv_nmos ', 'description', 'simulator', 'parameter_names', 'device_parameters'])\n"
]
}
],
"source": [
"print(lookup_table_nmos.keys())"
]
},
{
"cell_type": "code",
"execution_count": 5,
"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))\n",
"\n",
"rows_0, cols_0 = np.shape(nmos.extracted_table['gm']) # just for getting the shape of the data\n",
"rows_1, cols_1 = np.shape(pmos.extracted_table['gm']) # just for getting the shape of the data\n",
"reshaped_lengths_nmos = np.tile(nmos.length[:, np.newaxis], (1, cols_0))\n",
"reshaped_lengths_pmos = np.tile(pmos.length[:, np.newaxis], (1, cols_1))"
"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": 6,
"execution_count": 37,
"id": "b27d5fca-3436-4df7-895f-f6a4bbd7a80d",
"metadata": {
"jupyter": {
@ -76,11 +53,11 @@
" 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",
" 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",
@ -201,6 +178,23 @@
" })\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",
@ -247,6 +241,18 @@
" \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",
@ -257,14 +263,14 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 39,
"id": "b7cc630f-b385-47a6-a6f9-ac0d10effffe",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "b5ed71dbc38a4a55b7b1717f2b0ca7d2",
"model_id": "c0d716a395d84be68ee1dbc73a913175",
"version_major": 2,
"version_minor": 0
},
@ -283,7 +289,14 @@
"gds_values = nmos.extracted_table['gds']\n",
"vgs_values= nmos.extracted_table['vgs']\n",
"\n",
"plot_data_vs_data(gm_values/id_values, gm_values/gds_values, vgs_values, reshaped_lengths_nmos, 'gm/id', 'gm/gds')"
"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",
")"
]
},
{
@ -296,14 +309,14 @@
},
{
"cell_type": "code",
"execution_count": 111,
"execution_count": 40,
"id": "3727c42d-a4bf-4eb0-bc11-6e859ae41324",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "25e14d69e1084f71a4f21a94fe991a02",
"model_id": "cba5a4c794944ea1900cd54144dad957",
"version_major": 2,
"version_minor": 0
},
@ -322,7 +335,7 @@
"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, reshaped_lengths_pmos, 'gm/id', 'gm/gds')"
"plot_data_vs_data(gm_values/id_values, gm_values/gds_values, vgs_values, length_2d_pmos, 'gm/id', 'gm/gds')"
]
}
],

View File

@ -1,8 +1,15 @@
import os
from mosplot.lookup_table_generator.simulators import NgspiceSimulator, HspiceSimulator
from mosplot.lookup_table_generator import LookupTableGenerator, TransistorSweep
# One of `include_paths` or `lib_mappings` must be specified.
# The rest are optional.
# Read environment variables to build model path
PDK_ROOT = os.environ["PDK_ROOT"]
PDK = os.environ["PDK"]
model_path = os.path.join(PDK_ROOT, PDK, "libs.tech/ngspice/models/cornerMOSlv.lib")
ngspice = NgspiceSimulator(
# Provide path to simulator if simulator is not in system path.
simulator_path="ngspice",
@ -15,8 +22,8 @@ ngspice = NgspiceSimulator(
# Files to include with `.LIB`.
lib_mappings = [
("/home/pedersen/IHP-Open-PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib", " mos_tt") # Put your own path to the corner lib
lib_mappings = [
(model_path, "mos_tt")
],
# If the transistor is defined inside a subcircuit in

View File

@ -1,8 +1,15 @@
import os
from mosplot.lookup_table_generator.simulators import NgspiceSimulator, HspiceSimulator
from mosplot.lookup_table_generator import LookupTableGenerator, TransistorSweep
# One of `include_paths` or `lib_mappings` must be specified.
# The rest are optional.
# Read environment variables to build model path
PDK_ROOT = os.environ["PDK_ROOT"]
PDK = os.environ["PDK"]
model_path = os.path.join(PDK_ROOT, PDK, "libs.tech/ngspice/models/cornerMOSlv.lib")
ngspice = NgspiceSimulator(
# Provide path to simulator if simulator is not in system path.
simulator_path="ngspice",
@ -15,8 +22,8 @@ ngspice = NgspiceSimulator(
# Files to include with `.LIB`.
lib_mappings = [
("/home/pedersen/IHP-Open-PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib", " mos_tt") # Put your own path to the corner lib
lib_mappings = [
(model_path, "mos_tt")
],
# If the transistor is defined inside a subcircuit in

View File

@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 19,
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
@ -17,7 +17,7 @@
},
{
"cell_type": "code",
"execution_count": 20,
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
@ -27,7 +27,7 @@
},
{
"cell_type": "code",
"execution_count": 22,
"execution_count": 3,
"metadata": {},
"outputs": [
{
@ -44,22 +44,17 @@
},
{
"cell_type": "code",
"execution_count": 23,
"execution_count": 4,
"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))\n",
"\n",
"rows_0, cols_0 = np.shape(nmos.extracted_table['gm']) # just for getting the shape of the data\n",
"rows_1, cols_1 = np.shape(pmos.extracted_table['gm']) # just for getting the shape of the data\n",
"reshaped_lengths_nmos = np.tile(nmos.length[:, np.newaxis], (1, cols_0))\n",
"reshaped_lengths_pmos = np.tile(pmos.length[:, np.newaxis], (1, cols_1))"
"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": 24,
"execution_count": 5,
"metadata": {
"jupyter": {
"source_hidden": true
@ -71,11 +66,11 @@
" 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",
" 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",
@ -198,6 +193,22 @@
" 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",
@ -243,6 +254,17 @@
" \n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"### ------ automated flattening based on shape ------### \n",
"reshaped_lengths_pmos = tile_length_to_match_data(pmos.length, pmos.extracted_table['gm'])\n",
"reshaped_lengths_nmos = tile_length_to_match_data(nmos.length, nmos.extracted_table['gm'])"
]
},
{
"attachments": {
"image.png": {
@ -264,7 +286,7 @@
},
{
"cell_type": "code",
"execution_count": 25,
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
@ -278,7 +300,7 @@
},
{
"cell_type": "code",
"execution_count": 26,
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
@ -288,13 +310,13 @@
},
{
"cell_type": "code",
"execution_count": 34,
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "ea4f549168444a6da3039c78cb0ef5f0",
"model_id": "72114aa1708645529ec782f5479a3a60",
"version_major": 2,
"version_minor": 0
},
@ -308,8 +330,7 @@
],
"source": [
"nmos_M6 = Mosfet(lookup_table=lookup_table_nmos, mos=\"sg13_lv_nmos \", vbs=0, vds=0.6)\n",
"rows_0, cols_0 = np.shape(nmos_M6.extracted_table['gm'])\n",
"reshaped_lengths_nmos_M6 = np.tile(nmos_M6.length[:, np.newaxis], (1, cols_0))\n",
"reshaped_lengths_nmos_M6 = tile_length_to_match_data(nmos_M6.length, nmos_M6.extracted_table['gm'])\n",
"\n",
"width_values_M6 = nmos_M6.width\n",
"id_values_M6 = nmos_M6.extracted_table['id']\n",
@ -322,7 +343,7 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 10,
"metadata": {},
"outputs": [
{
@ -331,7 +352,7 @@
"7.949913192125772e-05"
]
},
"execution_count": 13,
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
@ -348,7 +369,7 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 11,
"metadata": {},
"outputs": [
{
@ -371,13 +392,13 @@
},
{
"cell_type": "code",
"execution_count": 38,
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "c13371af34b44b4fbcd6d05b532d361f",
"model_id": "d2c6f85fa97b4d9d8844e236d1b23f3f",
"version_major": 2,
"version_minor": 0
},
@ -391,8 +412,7 @@
],
"source": [
"pmos_M7 = Mosfet(lookup_table=lookup_table_pmos, mos=\"sg13_lv_pmos\", vbs=0, vds=-0.6, vgs=(-1.2, -0.1))\n",
"rows_0, cols_0 = np.shape(pmos_M7.extracted_table['gm'])\n",
"reshaped_lengths_pmos_M7 = np.tile(pmos_M7.length[:, np.newaxis], (1, cols_0))\n",
"reshaped_lengths_pmos_M7 = tile_length_to_match_data(pmos_M7.length, pmos_M7.extracted_table['gm'])\n",
"\n",
"width_values_M7 = pmos_M7.width\n",
"id_values_M7 = pmos_M7.extracted_table['id']\n",
@ -407,7 +427,7 @@
},
{
"cell_type": "code",
"execution_count": 39,
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
@ -418,7 +438,7 @@
},
{
"cell_type": "code",
"execution_count": 40,
"execution_count": 14,
"metadata": {},
"outputs": [
{
@ -441,13 +461,13 @@
},
{
"cell_type": "code",
"execution_count": 41,
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "2eca17df295841629e74fb72e1cff63f",
"model_id": "3423adf060a5431c9ed9dcfd25cdd5cb",
"version_major": 2,
"version_minor": 0
},
@ -461,7 +481,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "88996c8fb302472b8fb5add0523f26dc",
"model_id": "d9a07198c63c44209996a193ba18660b",
"version_major": 2,
"version_minor": 0
},
@ -480,7 +500,7 @@
},
{
"cell_type": "code",
"execution_count": 42,
"execution_count": 16,
"metadata": {},
"outputs": [
{
@ -550,7 +570,7 @@
},
{
"cell_type": "code",
"execution_count": 43,
"execution_count": 17,
"metadata": {},
"outputs": [
{
@ -559,7 +579,7 @@
"2.0004294796936965e-06"
]
},
"execution_count": 43,
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
@ -577,7 +597,7 @@
},
{
"cell_type": "code",
"execution_count": 44,
"execution_count": 18,
"metadata": {},
"outputs": [
{
@ -597,7 +617,7 @@
},
{
"cell_type": "code",
"execution_count": 45,
"execution_count": 19,
"metadata": {},
"outputs": [
{
@ -625,13 +645,13 @@
},
{
"cell_type": "code",
"execution_count": 47,
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "5d7b576ad312473eaeab532b93f3e92f",
"model_id": "bc344d417b284a71abea7fb90081872e",
"version_major": 2,
"version_minor": 0
},
@ -645,7 +665,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "e116eacdd44e4571b3f183da0118a4fa",
"model_id": "b312811f418442868110cc154d1cd4b7",
"version_major": 2,
"version_minor": 0
},
@ -660,11 +680,8 @@
"source": [
"pmos_M12 = Mosfet(lookup_table=lookup_table_pmos, mos=\"sg13_lv_pmos\", vbs=-0.2, vds=-0.6, vgs=(-1.2, -0.1))\n",
"nmos_M34 = Mosfet(lookup_table=lookup_table_nmos, mos=\"sg13_lv_nmos \", vbs=0, vds=0.6)\n",
"rows_0, cols_0 = np.shape(nmos_M34.extracted_table['gm']) # just for getting the shape of the data\n",
"rows_1, cols_1 = np.shape(pmos_M12.extracted_table['gm']) # just for getting the shape of the data\n",
"reshaped_lengths_nmos_M34 = np.tile(nmos_M34.length[:, np.newaxis], (1, cols_0))\n",
"reshaped_lengths_pmos_M12 = np.tile(pmos_M12.length[:, np.newaxis], (1, cols_1))\n",
"\n",
"reshaped_lengths_nmos_M34 = tile_length_to_match_data(nmos_M34.length, nmos_M34.extracted_table['gm'])\n",
"reshaped_lengths_pmos_M12 = tile_length_to_match_data(pmos_M12.length, pmos_M12.extracted_table['gm'])\n",
"\n",
"id_values_M12 = pmos_M12.extracted_table['id']\n",
"gm_values_M12 = pmos_M12.extracted_table['gm']\n",
@ -682,7 +699,7 @@
},
{
"cell_type": "code",
"execution_count": 48,
"execution_count": 26,
"metadata": {},
"outputs": [
{
@ -709,13 +726,13 @@
},
{
"cell_type": "code",
"execution_count": 50,
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "4cca567128c14cb68100041e1a57c919",
"model_id": "e503a959702844d09e52f5cc34072b5b",
"version_major": 2,
"version_minor": 0
},
@ -729,7 +746,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "6c92cedcecfb47f491e2b860b0c65a31",
"model_id": "9299f09d02b1408b88c4ad86a163ce34",
"version_major": 2,
"version_minor": 0
},
@ -751,7 +768,7 @@
},
{
"cell_type": "code",
"execution_count": 51,
"execution_count": 28,
"metadata": {},
"outputs": [
{
@ -804,7 +821,7 @@
},
{
"cell_type": "code",
"execution_count": 52,
"execution_count": 29,
"metadata": {},
"outputs": [
{
@ -906,13 +923,6 @@
"\"\"\"\n",
"print(summary)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {