change gmid content

This commit is contained in:
PhillipRambo 2025-04-22 14:58:44 +02:00
parent 225bf00ee2
commit f011515ebc
33 changed files with 286 additions and 24976 deletions

1
.gitignore vendored
View File

@ -13,4 +13,5 @@ modules/module_3_8_bit_SAR_ADC/part_2_digital_comps/bootstrap_switch/python/
*.so
*.out
*.vcd
*.npz

View File

@ -1,431 +0,0 @@
# Modified gmid Script for IHP Open PDK
This repository contains a modified version of the original [gmid](https://github.com/medwatt/gmid) script, adapted specifically for use with the IHP Open PDK.
## Acknowledgements
We extend our thanks to [medwatt](https://github.com/medwatt) for providing a robust environment for generating GmID lookup tables with NgSpice.
## Credits
All credit for the original repository and script goes to [medwatt](https://github.com/medwatt).
## Changes and Enhancements
- **Terminal GUI**: A terminal-based GUI was developed to facilitate easy selection of MOSFETs in the IHP Open PDK for generating LUTs.
- **LUT Class**: The lookup table generator was modified to ensure compatibility with the IHP Open PDK netlisting format.
- **Automation Handling**: A dedicated handling script was created to automate the GUI process for improved workflow.
# MOSFET Characterization in Python
## Motivation
This tool has the following goals:
1. Provide an easy way of creating plots of MOSFET parameters, such as those
used in the gm/ID design methodology.
2. Provide a tool that does not depend on any proprietary software or require
licensing fees.
3. Open source so that it can be easily modified/extended by the user.
## Installation
### Requirements
This tools is written in Python and requires the following:
- `Numpy`, `Scipy`, and `Matplotlib` for data analysis and plotting.
- [`ngspice`](https://ngspice.sourceforge.io/) or `hspice` for generating the
lookup table.
### Installation
- Clone this repository: `git clone https://github.com/medwatt/gmid.git`.
- Inside the directory, invoke: `pip install .`.
## Generating a Lookup Table
Before any plots can be made, a lookup table of all the relevant parameters
must first be created. This is done by instantiating an object from the
`LookupTableGenerator` and then building the table with the `build` method. An
example is given below.
```python
from mosplot import LookupTableGenerator
obj = LookupTableGenerator(
description="freepdk 45nm ngspice",
simulator="ngspice",
simulator_path="/usr/bin/ngspice", # optional
model_paths=[
"/home/username/gmid/models/NMOS_VTH.lib",
"/home/username/gmid/models/PMOS_VTH.lib",
],
model_names={
"nmos": "NMOS_VTH",
"pmos": "PMOS_VTH",
},
vsb=(0, 1.0, 0.1),
vgs=(0, 1.0, 0.01),
vds=(0, 1.0, 0.01),
width=10e-6,
lengths=[50e-9, 100e-9, 200e-9, 400e-9, 800e-9, 1.6e-6, 3.2e-6, 6.4e-6],
)
obj.build("/home/username/gmid/lookup_tables/freepdk_45nm_ngspice.npy")
```
A summary of some of the parameters is given below:
- The simulator used is specified with the `simulator` parameter. At the
moment, only `ngspice` and `hspice` are supported. If you're using windows or
some linux distribution where `ngspice` and `hspice` are named differently,
you will have to pass the full path to the binary to the `simulator_path`
variable.
- The lookup_table will be generated for a specific transistor model. Provide
the location of the model files as a list using the `model_paths` parameter.
Since it is possible to have more than one model definition inside a file,
you need to specify the model name. This is done via the `model_names`
parameter, where the keys are always `"nmos"` and `"pmos` and their values
are the names of the models to be used.
- If there's a specific need to pass in some custom SPICE commands, these
should be done via the `raw_spice` parameter (not shown in the example above).
- To generate a lookup table, the bulk, gate, and drain voltages relative to
the source have to be swept over a range of voltages. Specify the range in
the form `(start, stop, step)`. The smaller the step size, the bigger is the
size of the lookup table.
- The `lengths` can be provided as a list of discrete values or a 1-dimensional
`numpy` array.
- Only a single `width` should be provided. The assumption here is that the
parameters of the MOSFET scale linearly with the width. Because of this
assumption, all parameters that are width-dependent must be de-normalized
with respect to the current or width that you're working with.
- The directory where the generated lookup table is saved is passed directly to
the `build` method.
## Using the Tool
Because of the interactive nature of designing analog circuits, using this
script within a `jupyter` notebook is highly recommended.
### Imports
We begin by making the following imports:
```python
import numpy as np
from mosplot import load_lookup_table, LoadMosfet
```
The `load_lookup_table` function loads a lookup table such as the one generated
in the previous section.
```python
lookup_table = load_lookup_table("path/to/lookup-table.npy")
```
The `LoadMosfet` class contains methods that can be used to generate plots
seamlessly. If you plan to modify the style of the plots or plot things
differently, you will also have to import `matplotlib`.
```python
import matplotlib.pyplot as plt
plt.style.use('path/to/style')
```
### Making Simple Plots
We start by creating an object called `nmos` that selects the NMOS
from the lookup table and sets the source-bulk and drain-source voltages to
some fixed values. Since the data is 4-dimensional, it is necessary to fix two
of the variables at a time to enable 2-dimensional plotting.
```python
nmos = LoadMosfet(lookup_table=lookup_table, mos="nmos", vsb=0.0, vds=0.5, vgs=(0.3, 1))
```
The above code filters the table at `vsb=0.0` and `vds=0.5` for all `lengths`
and for `vgs` values between `(0.3, 1)`. You can also include a step such as
`(0.3, 1, 0.02)`. If you want all values of `vgs`, either set it to `None` or
don't include it.
Methods are available to create the most commonly-used plots in the gm/ID
methodology so that you don't have to type them. These are:
- `current_density_plot()`: this plots $I_{D}/W$ vs $g_{m}/I_{D}$.
- `gain_plot()`: this plots $g_m / g_{ds}$ vs $g_{m}/I_{D}$.
- `transit_frequency_plot()`: this plots $f_{T}$ vs $g_{m}/I_{D}$.
- `early_voltage_plot()`: this plots $V_{A}$, vs $g_{m}/I_{D}$.
For example, the plot of $I_{D}/W$ vs $g_{m}/I_{D}$ is shown below.
```python
nmos.current_density_plot()
```
![current density plot](./figures/nmos_current_density.svg)
When the lookup table includes a lot of lengths, the plot can become crowded.
You can pass a list of lengths to plot with the `length` parameter.
Use `nmos.lengths` to get a list of all the lengths in the lookup table.
```
array([5.0e-08, 1.0e-07, 2.0e-07, 4.0e-07, 8.0e-07, 1.6e-06, 3.2e-06,
6.4e-06])
```
Pass a filtered list to the `current_density_plot` method.
```python
nmos.current_density_plot(
lengths = [5.0e-08, 1.0e-07, 2.0e-07]
)
```
![current density plot](./figures/nmos_current_density_filtered.svg)
Note that the tool does its best to determine how to scale the axes. For
example, in the last plot, a `log` scale was chosen for the y-axis. We can
easily overwrite that, as well as other things.
```python
nmos.current_density_plot(
lengths = [5.0e-08, 1.0e-07, 2.0e-07],
y_scale = 'linear',
x_limit = (5, 20),
y_limit = (0, 300),
save_fig="path/to/save/figure/with/extension"
)
```
![current density plot](./figures/nmos_current_density_options.svg)
### Plotting by Expression
Now, suppose we want to plot something completely custom. The example below
shows how.
```python
nmos.plot_by_expression(
x_expression = nmos.vgs_expression,
y_expression = {
"variables": ["id", "gds"],
"function": lambda x, y: x / y,
"label": "$I_D / g_{ds} (A/S)$"
},
)
```
![custom expression](./figures/nmos_custom_expression_1.svg)
For this example, we want $V_{\mathrm{GS}}$ on the x-axis. Since $V_{\mathrm{GS}}$ is such a
commonly-used expression, it is already defined in the code. Other
commonly-used expressions are also defined, such as:
- `gmid_expression`
- `vgs_expression`
- `vds_expression`
- `vsb_expression`
- `gain_expression`
- `current_density_expression`
- `transist_frequency_expression`
- `early_voltage_expression`
For the y-axis, we want a custom expression that uses the parameters $I_D$ and
$g_{\mathrm{ds}}$. This can be done by defining a dictionary that specifies the
variables needed and how to calculate the required parameter. The `label` field
is optional. The function field is also optional if we want to just plot the
parameter, as shown in the example below.
```python
nmos.plot_by_expression(
x_expression = nmos.vgs_expression,
# y_expression = nmos.id_expression, ## same as below
y_expression = {
"variables": ["id"],
"label": "$I_D (A)$"
}
)
```
![custom expression](./figures/nmos_custom_expression_2.svg)
## Looking Up Values
While having plots is a good way to visualize trends, we might also just be
interested in the raw value.
![gain expression](./figures/nmos_gain_plot.svg)
Looking at the figure above, it's hard to read the exact value on the y-axis
for a particular value on the x-axis, especially more so when the scale is
logarithmic. Also, what if we need to read the value for a length that
is not defined in our lookup table?
There are two ways to go about this:
- Zoom in and click on the plot. This prints out the `x` and `y`
coordinates. Note, in jupyter notebooks, you need to execute `%matplotlib
widget` or `%matplotlib qt` to interact with the plot.
- Use a lookup method to get a more precise value.
### Lookup Using Interpolation
The snippet below shows how we can lookup the `gain` given the `length` and
`gmid`. The returned value is calculated using interpolation from the available
data. The accuracy of the result depends on how far the points are from those
defined in the table.
```python
x = nmos.interpolate(
x_expression=nmos.lengths_expression,
x_value=100e-9,
y_expression=nmos.gmid_expression,
y_value=15,
z_expression=nmos.gain_expression,
)
```
The above code evaluates the `gain` at a single point. Suppose we want to know
the `gmid` or `length` for which `0.08 <= vdsat < 0.12` and `1e6 <= gds < 4e-6`.
The snippet below shows how.
```python
x = nmos.interpolate(
x_expression=nmos.vdsat_expression,
x_value=(0.08, 0.12, 0.01),
y_expression=nmos.gds_expression,
y_value=(1e-6, 4e-6, 1e-6),
z_expression=nmos.gmid_expression,
# z_expression=nmos.length_expression,
)
# 1e-6
array([[17.95041245, 17.89435802, 17.47526426], # 0.08
[16.87609489, 16.76595338, 16.53927928],
[14.77585736, 15.09803158, 14.9483348 ],
[14.12540234, 14.05481451, 14.04265227]])
```
### Lookup By Expression
`lookup_expression_from_table()` simply looks up an expression from the
table. It doesn't use any interpolation. So, make sure that the values you
are looking up are present in the table.
```python
x = nmos.lookup_expression_from_table(
lengths=100e-9,
vsb=0,
vds=(0.0, 1, 0.01),
vgs=(0.0, 1.01, 0.2),
primary="vds",
expression=nmos.current_density_expression,
)
```
## Plotting Methods
### Plot by Sweep
The `plot_by_sweep` method is extremely flexible and can be used to create
all sorts of plots. For example, the snippet below shows how to plot the
traditional output characteristic plot of a MOSFET.
```python
nmos.plot_by_sweep(
lengths=180e-9,
vsb = 0,
vds = (0.0, 1, 0.01), # you can also set to `None`
vgs = (0.0, 1.01, 0.2),
x_expression_expression = nmos.vds_expression,
y_expression_expression = nmos.id_expression,
primary = "vds",
x_eng_format=True,
y_eng_format=True,
y_scale='linear',
)
```
![output characteristic](./figures/nmos_output_characteristics.svg)
### Quick Plot
Let's say we want to see how $V_{\mathrm{DS}_{\mathrm{SAT}}}$ (the drain-source
voltage required to enter saturation) compares with $V_{\mathrm{OV}}$ and
$V^{\star} = \frac{2}{g_m / I_D}$ in a single plot. We can generate each of
these plots individually, as we did before, but ask the method to return the
plot data so that we can combine them in a single plot. Note that you can also
use `lookup_expression_from_table()` to return the required data if you don't
want to see the plot.
```python
vdsat = nmos.plot_by_expression(
lengths=[45e-9],
x_expression = nmos.vgs_expression,
y_expression = nmos.vdsat_expression,
return_result = True,
)
vov = nmos.plot_by_expression(
lengths=[45e-9],
x_expression = nmos.vgs_expression,
y_expression = {
"variables": ["vgs", "vth"],
"function": lambda x, y: x - y,
},
return_result = True,
)
vstar = nmos.plot_by_expression(
lengths=[45e-9],
x_expression = nmos.vgs_expression,
y_expression = {
"variables": ["gm", "id"],
"function": lambda x, y: 2 / (x/y),
},
return_result=True,
)
```
The result is returned in a tuple in the form `(x_data, y_data)`. We can then
make any custom plot using `matplotlib`. Nevertheless, there's a method called
`quick_plot()` that formats the plot in the same way as the generated plots.
`quick_plot()` accepts `numpy` arrays, or a list of `x` and `y` values, as
shown in the example below.
```python
nmos.quick_plot(
x = [vdsat[0], vstar[0], vov[0]],
y = [vdsat[1], vstar[1], vov[1]],
legend = ["$V_{\\mathrm{DS}_{\\mathrm{SAT}}}$", "$V^{\\star}$", "$V_{\\mathrm{OV}}$"],
x_limit = (0.1, 1),
y_limit = (0, 0.6),
x_label = "$V_{\\mathrm{GS}}$",
y_label = "$V$",
)
```
![qucik plot](./figures/nmos_quick_plot.svg)
# Acknowledgment
- Parsing the output from `hspice` is done using
[this](https://github.com/HMC-ACE/hspiceParser) script.
- If you find this tool useful, it would be nice if you cite it.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 80 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 91 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 61 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 42 KiB

View File

@ -1,38 +0,0 @@
from mosplot import LookupTableGenerator
from mosplot.src.manu_man import description_menu, sweeping_menu, input_selection, transistor_menu
import os
def main():
pdk_type = input_selection()
model, transistor_model = transistor_menu() # model is a dictionary
description, simulator, model_path = description_menu(model)
vsb, vgs, vds, width, lengths = sweeping_menu()
# Create the LookupTableGenerator object with all parameters
obj = LookupTableGenerator(
simdisc=transistor_model,
description=description,
simulator=simulator,
model_paths=model_path,
model_names=model, # Directly use the model dictionary
vsb=vsb,
vgs=vgs,
vds=vds,
width=width,
lengths=lengths,
)
file_name = f"{description}"
file_path = os.path.join('LUTs', file_name)
os.makedirs('LUTs', exist_ok=True)
obj.build(file_path)
if __name__ == "__main__":
main()

View File

@ -1,55 +0,0 @@
# Matplotlib style for scientific plotting
# This is the base style for "SciencePlots"
# see: https://github.com/garrettj403/SciencePlots
# Set color cycle: blue, green, yellow, red, violet, gray
axes.prop_cycle : cycler('color', ['0C5DA5', '00B945', 'FF9500', 'FF2C00', '845B97', '474747', '9e9e9e'])
# Set default figure size
figure.figsize : 3.5, 2.625
# Set x axis
xtick.direction : in
xtick.major.size : 3
xtick.major.width : 0.5
xtick.minor.size : 1.5
xtick.minor.width : 0.5
xtick.minor.visible : True
xtick.top : True
# Set y axis
ytick.direction : in
ytick.major.size : 3
ytick.major.width : 0.5
ytick.minor.size : 1.5
ytick.minor.width : 0.5
ytick.minor.visible : True
ytick.right : True
# Set line widths
axes.linewidth : 0.5
grid.linewidth : 0.5
lines.linewidth : 1.
# Remove legend frame
legend.frameon : False
# Always save as 'tight'
savefig.bbox : tight
savefig.pad_inches : 0.05
savefig.transparent : True
# Increase global font size
font.size : 14
# Use sans-serif fonts
font.sans-serif : cm
font.family : sans-serif
# Use CM Sans Serif for math text
axes.formatter.use_mathtext : True
mathtext.fontset : cm
# Use LaTeX for math formatting
text.usetex : True
text.latex.preamble : \usepackage{amsmath} \usepackage{amssymb}

View File

@ -1,4 +0,0 @@
from .src.main import LoadMosfet, load_lookup_table
from .src.lookup_table_generator import LookupTableGenerator
__all__ = ['LoadMosfet', 'load_lookup_table', 'LookupTableGenerator']

View File

@ -1,615 +0,0 @@
"""
Author: Raphael Gonzalez (RAffA), Mathew Spencer
github: RaffaGonzo
Copyright 2021 Raphael Gonzalez, Mathew Spencer
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
import struct
from os.path import basename, split, join
from os import mkdir
from shutil import rmtree
import pickle
import sys
COUNT = 0
def read_data_block(data_block, vrsn):
data_format = {'9601': ('<f', 4), # float
'2001': ('<d', 8)} # double precision
assert vrsn in data_format.keys(), 'only the 2001 and 9601 formats are supported'
data_bytes = (data_block[0 + i: data_format[vrsn][1] + i] for i in range(0, len(data_block), data_format[vrsn][1])) # break the block into chunks of 4 (or 8) bytes each depending on the encoding..?
block_lst = []
for byte_set in data_bytes:
val = struct.unpack(data_format[vrsn][0], byte_set)[0]
if val == 1.0000000150474662e+30 or val == 1e+30: # I think there may be a precision error | 32 bit doesn't have the same level of precision as 64 bit... but neither of these is large for float
global COUNT
COUNT += 1
block_lst.append(val)
return block_lst
def read_binary_signal_file(file_path):
"""
reads the binary file (Hspice output for POST = 1)
:param file_path: path to file, taken as a string
:return: file_header_block which contains information about the test-bench settings, data_list which is a 2D list containing the floating point
"""
with open(file_path, 'rb') as file:
decode_info = file.read(16) # this is the block header, first 4 bytes is an endian indicator, next 4 is a blank, then another endian indicator, then last 4 are the block size
block_size = decode_info[-4:]
block_size_int = struct.unpack('<i', block_size)[0] # the struct will return a tuple no matter what, since we know there is one element in the tuple we can extract it on the spot
file_header_block = file.read(block_size_int).decode('UTF-8')
version = file_header_block[20:24]
version = version.strip(' ')
if not version:
version = file_header_block[16:20]
footer = file.read(4)
data_list = []
while True:
block_header = file.read(16)
if not block_header: # this allows for the assertion to not get invoked when it reaches the end of the file
break
block_size = block_header[-4:]
block_size_int = struct.unpack('<i', block_size)[0]
data = file.read(block_size_int)
data_out = read_data_block(data, version)
data_list.append(data_out)
footer = file.read(4)
assert struct.unpack('<i', footer)[0] == block_size_int, 'footer size: ' + str(struct.unpack('<i', footer)[0]) + ' header size: ' + str(block_size_int) # a check against any errors or corruption
return file_header_block, data_list
def get_var_name_idx(var_lst):
for i, item in enumerate(var_lst):
try:
int(item)
except ValueError:
return i
return None
def parse_var_name(name):
name_parts = name.split('(')
new_name_parts = []
for nm in name_parts:
nm = nm.replace('.', '_')
nm = nm.replace(':', '_')
new_name_parts.append(nm)
try:
if new_name_parts[0] == new_name_parts[1]:
return new_name_parts[0]
return new_name_parts[0] + '_' + new_name_parts[1]
except IndexError:
return new_name_parts[0]
def parse_header(header_str):
header_lst = header_str.split(' ')
header_lst = list(filter(lambda x: x != '', header_lst))
header_lst = header_lst[:2] + header_lst[15:] # this chops out the copy right stuff
analysis_type = header_lst[2]
var_info = header_lst[3:-1]
first_var_units = {
'1': 'sec',
'2': 'freq',
'3': 'volt'
}
gnrl_var_units = {
'1': 'volt', # 1 voltage as dependent var in sweep or transient
'2': 'volt', # 2 voltage as dependent var in AC analysis
'8': 'Amp', # 8 current
'9': 'Amp', # 9 magnitude of current (not verified)
'10': 'Amp', # 10 real part of current (not verified)
'11': 'iAmp', # 11 imaginary part of current (not verified)
'15': 'Amp', # 15 current
}
# from https://github.com/krichter722/gwave/blob/master/doc/hspice-output.txt
# First comes an integer for each variable, indicating the variable type.
#
# The first variable is the independent variable. The integral types for this
# also indicates the analysis type.
# 1 Transient analysis; type is time.
# 2 AC analysis; type is frequency
# 3 DC sweep; type is voltage
#
# The type numbers for the dependent variables are:
#
# 1 voltage as dependent var in sweep or transient
# 2 voltage as dependent var in AC analysis
# 8 current
# 9 magnitude of current (not verified)
# 10 real part of current (not verified)
# 11 imaginary part of current (not verified)
# 15 current
# 16 the units stuff is not used but perhaps it would be useful to other people.
var_idx = get_var_name_idx(var_info)
var_nums, var_names = var_info[:var_idx], var_info[var_idx:] # these two variables should have identical length
var_names = [parse_var_name(name) for name in var_names]
return len(var_names), var_names
def dict_to_matlab_str(data_dict):
rtn_str = ''
for key, arr in data_dict.items():
rtn_str += key + '= {'
arr_str = str(arr)
arr_str = arr_str[1:-1]
arr_str = arr_str.strip(',')
arr_str = arr_str.replace('],', '];\n')
rtn_str += arr_str + '};'
return rtn_str
def dict_to_csv_str(data_dict):
key_lst = list(data_dict.keys())
csv_str = ''
for key in key_lst: csv_str += key + ', '
csv_str = csv_str[:-2] + '\n' # init the csv str
for i in range(len(data_dict[key_lst[0]])): # essentially for each sweep
for j in range(len(data_dict[key_lst[0]][i])): # for each item index inside the 2d arr
for key in key_lst: # select the right column in a defined order
try:
csv_str += str(data_dict[key][i][j]) + ', '
except IndexError: # only should happen when there is a sweep
try:
csv_str += ' , '
except NameError:
pass
csv_str = csv_str[:-2] + '\n'
csv_str = csv_str[:-1] # take out final return char
return csv_str
def dict_to_mat_obj(data_dict):
if sio in sys.modules and np in sys.modules:
for var, sweep_lst in data_dict.items():
data_dict[var] = np.array(sweep_lst)
return data_dict
def break_by_sweep(data_lst, vrsn):
"""
breaks all the data pulled from .tr* file into sweeps, if there are none it will just returns a 1 x n 2d list
:param data_lst: a list with the values read from the binary file
:param vrsn: the version of binary file being parsed
:return: 2d list of data separated by sweep
"""
vrsn_dict = {
'2001': 1e30,
'9601': 1.0000000150474662e+30
}
if not data_lst:
return []
i = data_lst.index(vrsn_dict[vrsn])
return [data_lst[:i]] + break_by_sweep(data_lst[i + 1:], vrsn)
def general_make_dict(var_lst, data_lst, header_str, MULTI_SWEEP_FLAG):
var_dict = {var: [] for var in var_lst}
if MULTI_SWEEP_FLAG:
var_lst = var_lst[:-1]
sweep_val_lst = []
else:
sweep_val_lst = [] # won't have anything added to it in this case though
var_count = len(var_lst)
for i, sweep in enumerate(data_lst):
if MULTI_SWEEP_FLAG:
if int(sweep[0]) == sweep[0]:
sweep_val_lst.append(int(sweep[0]))
else:
sweep_val_lst.append(float(sweep[0]))
sweep = sweep[1:]
for var in var_dict.keys():
var_dict[var].append([])
for j, val in enumerate(sweep):
var = var_lst[j % var_count]
var_dict[var][i].append(val)
header_lst = header_str.split(' ')
header_lst = list(filter(lambda x: x != '', header_lst))
header_lst = header_lst[17:]
dict_key = ''
for var in header_lst[:-1]: # by the end of this loop if there is a sweep dict_key will have the sweep_var
try:
int(var)
except ValueError:
dict_key = var
for i, lst in enumerate(var_dict[parse_var_name(dict_key)]): var_dict[parse_var_name(dict_key)][i] = lst
if MULTI_SWEEP_FLAG:
return var_dict, MULTI_SWEEP_FLAG, dict_key, sweep_val_lst
return var_dict, MULTI_SWEEP_FLAG, None, None
def ac_make_dict(var_lst, data_lst, header_str, MULTI_SWEEP_FLAG):
var_lst = [['HERTZ']] + [[f'{parse_var_name(var)}_Mag', f'{parse_var_name(var)}_Phase'] for var in var_lst if var != 'HERTZ'] # increase the # of vars and prep for Re and Im parts to each
var_lst = sum(var_lst, []) # flatten
var_dict = {var: [[] for i in range(len(data_lst))] for var in var_lst}
var_count = len(var_lst)
for sweep_idx, lst in enumerate(data_lst):
i = 0
while i < len(lst):
if i % var_count == 0:
var_dict['HERTZ'][sweep_idx].append(lst[i])
i += 1
else:
var_dict[var_lst[i % var_count]][sweep_idx].append(lst[i])
var_dict[var_lst[(i % var_count) + 1]][sweep_idx].append(lst[i + 1])
i += 2
return var_dict, MULTI_SWEEP_FLAG, None, None # the Nones make this function play nice with the way I used it later.
def write_to_dict(data_lst, header_str, ext):
# this function puts it all together so that we get a dictionary with variable names for keys and lists of the respective values. If there are multiple sweeps then there will be multiple lists, the lists are always 2D
var_count, var_lst = parse_header(header_str)
# var_dict = {}
version = header_str[20:24]
version = version.strip(' ')
if not version:
version = header_str[16:20]
data_lst = sum(data_lst, [])
data_lst = break_by_sweep(data_lst, version)
MULTI_SWEEP_FLAG = len(data_lst) > 1
assert COUNT > 0 # for debugging
func_dict = {'tr': general_make_dict,
'sw': general_make_dict,
'ac': ac_make_dict}
return func_dict[ext](var_lst, data_lst, header_str, MULTI_SWEEP_FLAG)
def write_to_file(write_path, file_content, ext=None):
if type(file_content) == str:
with open(write_path, 'w+') as file:
file.write(file_content)
elif ext == 'mat':
sio.savemat(write_path, file_content)
else:
with open(write_path, 'wb') as file:
pickle.dump(file_content, file, protocol=pickle.HIGHEST_PROTOCOL)
def get_outfile_name(path, ext=None):
file_name = basename(path)
outfile_name = file_name.replace('.', '_')
if ext is not None:
outfile_name += f'.{ext}'
return outfile_name
def import_export_binary(path, ext, from_ext):
"""
this function will import *.tr*, *.sw*, and *.ac* binary and put a similarly named csv, matlab *.m, or python.pickle file/ folder of files in the same directory
:param path: (str) path to the *.tr* binary to be imported
:param ext: (str) the extension of the output file [only options *.m, *.csv, and *.pickle]
:return: None
"""
dict_to_pickle_dict = lambda dict_data: dict_data
ext_dict = {'m': dict_to_matlab_str,
'csv': dict_to_csv_str,
'pickle': dict_to_pickle_dict,
'mat': dict_to_mat_obj}
try:
header, data = read_binary_signal_file(path)
main_dict, sweep_flag, sweep_var_name, sweep_values = write_to_dict(data, header, from_ext)
# use sweep_flag to tell if it should be a folder of csvs or single file
if sweep_flag and ext == 'csv':
content = ''
fpath = path + '_' + sweep_var_name + '('
outfile_name = get_outfile_name(path, ext)
folder_name = get_outfile_name(outfile_name) # gets rid of dot
og_path = str(split(fpath)[0])
path = join(og_path, folder_name)
try:
mkdir(path)
except FileExistsError: # this will overwrite an existing folder with the same name
rmtree(path)
mkdir(path)
for i in range(len(list(main_dict.values())[0])):
temp_dict = {}
for key in main_dict.keys():
temp_dict[key] = [main_dict[key][i]]
file_name = get_outfile_name(fpath) + str(sweep_values[i]) + ').csv'
full_path = join(path, file_name)
content = dict_to_csv_str(temp_dict)
write_to_file(full_path, content)
else:
outfile_name = get_outfile_name(path, ext)
dir_path, _ = split(path)
file_path = join(str(dir_path), outfile_name)
content = ext_dict[ext](main_dict)
write_to_file(file_path, content, ext)
return main_dict, content
except KeyError:
raise ValueError('the extension must NOT have the "." in it ex: "csv" | the only extension options are m, csv, and pickle')
except FileNotFoundError as err:
print(err)
def import_export_ascii(path, ext):
dict_to_pickle_dict = lambda dict_data: dict_data
ext_dict = {'m': dict_to_matlab_str,
'csv': dict_to_csv_str,
'pickle': dict_to_pickle_dict,
'mat': dict_to_mat_obj}
try:
main_dict = signal_file_ascii_read(path)
content_obj = ext_dict[ext](main_dict)
sweep_flag = len(list(main_dict.values())[0]) > 1
sweep_var_name = ''
sweep_values = []
for key, val in main_dict.items():
if len(val[0]) == 1 and sweep_flag:
sweep_var_name = key
sweep_values = sum(val, [])
break
if sweep_flag and ext == 'csv':
fpath = f'{path}_{sweep_var_name}('
outfile_name = get_outfile_name(path, ext)
folder_name = get_outfile_name(outfile_name) # gets rid of dot
og_path = str(split(fpath)[0])
path = join(og_path, folder_name)
try:
mkdir(path)
except FileExistsError: # this will overwrite an existing folder with the same name
rmtree(path)
mkdir(path)
for i in range(len(list(main_dict.values())[0])):
temp_dict = {}
for key in main_dict.keys():
temp_dict[key] = [main_dict[key][i]]
file_name = get_outfile_name(fpath) + str(sweep_values[i]) + ').csv'
full_path = join(path, file_name)
content = dict_to_csv_str(temp_dict)
write_to_file(full_path, content)
else:
outfile_name = get_outfile_name(path, ext)
dir_path, _ = split(path)
file_path = join(str(dir_path), outfile_name)
write_to_file(file_path, content_obj, ext)
return main_dict, content_obj
except KeyError:
raise ValueError('the only extension options are "m," "csv," or "pickle"')
except FileNotFoundError as err:
print(err)
def usage():
usage_str = """Usage:
python Hspice_parse <input file path> <extension of output file(s)>
*** note: This parser only supports the 2001, 9601 formats
designated by ".option post_version=9601" for example
supported input file types are:
- AC simulations *.ac*
- DC sweeps *.sw*
- Transient simulations *.tr*
supported output file types are:
- CSV note that if there are multiple sweeps in the input
file this option will output a folder of CSVs
- pickle
- matlab's *.m
-h or --help to get this message
"""
print(usage_str)
def get_from_ext(path): # this function is for auto detecting the input file extention
file_name = basename(path)
name_components = file_name.split('.')
entire_ext = name_components[-1]
return entire_ext[:2] # returns the first two chars of the extension i.e. "ac" "sw" "tr" and ignores the numbers after
def reformat(sweep_lst):
"""
This function will change the original output of signal_file_ascii_read() to the format that
the write to dict function makes.
:param sweep_lst: list of dictionaries
:return: dictionary of 2D lists
"""
rtn_dict = {key: [] for key in sweep_lst[0].keys()}
for sweep in sweep_lst:
for key, value in sweep.items():
rtn_dict[key].append(value)
return rtn_dict
###################### ascii parsing ###################
# author: Mathew Spencer
# class simulation_result():
# """All the results germane to a single hSpice simulation including
# results, measures, parameters, temperature and alter number.
#
# Contains dicts called 'results' 'measures' 'parameters' and
# 'misc' each of which is keyed with named values and has values
# corresponding to the results.
# """
# pass
# class sweep_result():
# """Aggregates the results of individual simulations
# """
# pass
def signal_file_ascii_read(post2file):
"""Accepts a raw sweep or measure file from spice in ASCII format
(.tr*, .sw*, .mt*, etc.) and returns a list of dicts containing the
signal and parameter names as keys. Signal name keys have an array
containing the simulation results as values. Parameter name keys have
the parameter as a value. There is one dict in the array for each
simulation sweep point."""
## Preamble part of header
f = open(post2file)
l = f.readline() # 1st line contains string w/ # of variables
nauto = int(l[0:4])
nprobe = int(l[4:8])
nsweepparam = int(l[8:12])
l = f.readline() # 2nd and 3rd lines are copyright and stuff
l = f.readline()
ndataset = int(l.split()[-1]) # 3rd line ends with the number of data sets
## Number and name part of header
l = f.readline() # 4th line+ useful, but may be wrapped
while l.find('$&%#') == -1:
l = l + f.readline()
l = l.replace('\n', '')
simparams = l.split()[:-1] # Throw away terminator string
datatypes = simparams[0:nauto + nprobe] # NOTUSED
varnames = simparams[nauto + nprobe:2 * (nauto + nprobe)]
paramnames = simparams[2 * (nauto + nprobe):]
# Transform varnames and paramnames for Matlab
varnames = [x.partition('(')[0] if x.startswith('x') else x for x in varnames]
varnames = [x.replace('(', '_') for x in varnames]
varnames = [x.replace('.', '_') for x in varnames]
varnames = [x.replace(':', '_') for x in varnames]
paramnames = [x.replace(':', '_') for x in paramnames]
paramnames = ['param_' + x for x in paramnames]
# Read data block
all_sweep_results = []
l = f.readline().strip()
while l:
# Set up data storage structure
this_sweep_result = {}
for name in varnames:
this_sweep_result[name] = []
for name in paramnames:
this_sweep_result[name] = 0
## Data block for one sweep
numbers = []
fieldwidth = l.find('E') + 4
while True:
lastrow = l.find('0.1000000E+31') != -1
while l:
numbers.append(float(l[0:fieldwidth]))
l = l[fieldwidth:]
if lastrow:
break
else:
l = f.readline().strip()
numbers = numbers[:-1] # throw away terminator
params = numbers[:nsweepparam]
for index in range(len(varnames)):
this_sweep_result[varnames[index]] = numbers[nsweepparam + index::nauto + nprobe]
this_sweep_result.update(zip(paramnames, [[x] for x in params]))
all_sweep_results.append(this_sweep_result)
l = f.readline().strip()
f.close()
return reformat(all_sweep_results)
def signal_array_to_matlab_string(signal_array, accum=True): # Todo: This should be obsolete now!
""" Takes in a signal array produced by signal_file_read and returns a string that
is suitable for inclusion in a .m file to be loaded into matlab. Makes 2D arrays
for DC and AC sims. 1D arrays numbered by sweep for transients b/c length uneven.
"""
matlab_string = ''
if accum:
# first, slice across the signal array to gather like signals into 2D arrays
signal_accumulation = signal_array[0]
for name, signal in signal_accumulation.items():
signal_accumulation[name] = ['%.5e' % x for x in signal]
for simulation_result in signal_array[1:]:
for name, signal in simulation_result.items():
signal_accumulation[name].extend([';'] + ['%.5e' % x for x in signal])
# Second write out to a .m file string
for name, signal in signal_accumulation.items():
matlab_string = matlab_string + name + '= [' + ' '.join(signal) + '];\n'
else:
# some cases, esp transient sims, can't be accumulated b/c different sizes
matlab_string = ''
signal_accumulation = signal_array[0]
for name, signal in signal_accumulation.items():
signal_accumulation[name] = [['%.5e' % x for x in signal]]
for simulation_result in signal_array[1:]:
for name, signal in simulation_result.items():
signal_accumulation[name].append(['%.5e' % x for x in signal])
for name, signal_list in signal_accumulation.items():
matlab_string = matlab_string + name + '={'
for signal in signal_list:
matlab_string = matlab_string + '[ ' + ' '.join(signal) + '];\n'
matlab_string = matlab_string + '};'
return matlab_string
def measure_file_read(measure_file):
"""Reads a measure file and resturns a dict keyed on the names of the columns
with values that are a list of results"""
f = open(measure_file)
l = f.readline().strip() # 1st line auto generated
param_count = int(l.split('PARAM_COUNT=')[-1].strip())
l = f.readline().strip() # 2nd line is the comment
l = f.readline().strip() # 3rd line starts var names, last is alter#
while (l.find('alter#') == -1):
l = l + ' ' + f.readline().strip()
l = l.replace('#', '')
varnames = l.split()
varnum = len(varnames)
measure_result = {name: [] for name in varnames}
l = f.readline().strip() # 4th line starts data clumps to EOF
while l:
vals = l.split()
if len(vals) != varnum: # accumulate results over multiple lines
l = l + ' ' + f.readline().strip()
else: # then write to measure dict
for name, val in zip(varnames, vals):
if val == 'failed': # skip over failed measures
val = 0 # silent failures are the best kind!
measure_result[name].append([float(val)]) # todo that I added [] to make the lists 2D not sure if necessary
l = f.readline().strip()
f.close()
return measure_result
def measure_result_to_matlab_string(measure_result): # this should be obsolete now
matlab_string = ''
for name, signal in measure_result.items():
matlab_string = matlab_string + name + '= [' + ' '.join(['%.5e' % x for x in signal]) + '];\n'
return matlab_string
# end of Spencer's (lightly modified) code
def is_binary(file_path):
with open(file_path) as file:
try:
file.readline()
return False # this means that it is ascii
except UnicodeDecodeError:
return True # this means that it is binary
def import_export(path, ext):
if is_binary(path):
from_ext = get_from_ext(path)
import_export_binary(path, ext, from_ext)
else:
import_export_ascii(path, ext)
if __name__ == '__main__':
import sys
try:
if sys.argv[1] == '-h' or sys.argv[1] == '--help' or sys.argv[1] == '-help':
usage()
else:
path = sys.argv[1]
extension = sys.argv[2]
if extension == 'mat':
try:
import scipy.io as sio
import numpy as np
except ImportError:
print("To use Matlab's .mat extension, you must have Scipy and Numpy installed on your machine. See https://www.scipy.org/install.html and https://numpy.org/install for details.")
exit(1)
import_export(path, extension)
except:
usage()
resp_str = '\n\n This command needs the path to the file for import and the extension of the file being output.\n The extension can be either m, csv, or pickle. No other extensions are supported with this file at this time. If you are seeing this message, then something went wrong.'
print(resp_str)

View File

@ -1,63 +0,0 @@
import numpy as np
class NgspiceRawFileReader:
def __init__(self):
self.bsize_sp = 512
self.mdata_list = [
b"title",
b"date",
b"plotname",
b"flags",
b"no. variables",
b"no. points",
b"dimensions",
b"command",
b"option",
]
def read_file(self, fname):
"""Read ngspice binary raw files. Return tuple of the data, and the
plot metadata. The dtype of the data contains field names. This is
not very robust yet, and only supports ngspice.
>>> darr, mdata = rawread('test.py')
>>> darr.dtype.names
>>> plot(np.real(darr['frequency']), np.abs(darr['v(out)']))
"""
with open(fname, "rb") as fp:
plot = {}
arrs = []
plots = []
while True:
mdata = fp.readline(self.bsize_sp).split(b":", maxsplit=1)
if len(mdata) == 2:
key = mdata[0].lower()
if key in self.mdata_list:
plot[key] = mdata[1].strip()
elif key == b"variables":
nvars = int(plot[b"no. variables"])
npoints = int(plot[b"no. points"])
varspecs = [
fp.readline(self.bsize_sp).strip().decode("ascii").split()
for _ in range(nvars)
]
plot["varnames"], plot["varunits"] = zip(
*[(spec[1], spec[2]) for spec in varspecs]
)
elif key == b"binary":
rowdtype = np.dtype(
{
"names": plot["varnames"],
"formats": [
np.complex_
if b"complex" in plot[b"flags"]
else np.float_
]
* nvars,
}
)
arrs.append(np.fromfile(fp, dtype=rowdtype, count=npoints))
plots.append(plot.copy())
fp.readline() # Read to the end of line
else:
break
return (arrs, plots)

View File

@ -1,336 +0,0 @@
# -----------------------------------------------------------------------------#
# Author: Mohamed Watfa
# URL: https://github.com/medwatt/
# -----------------------------------------------------------------------------#
import os
import shutil
import pickle
import tempfile
import subprocess
from pick import pick
import numpy as np
from mosplot.src.manu_man import transistor_menu
from ..parsers.ngspice_parser import NgspiceRawFileReader
from ..parsers.hspice_parser import import_export
################################################################################
def range_to_arr(r):
start, stop, step = r
return np.arange(start, stop + step, step)
class LookupTableGenerator:
def __init__(
self,
identifier=None,
simdisc="*n.xm1.nsg13_lv_nmos",
vgs=(0, 1, 0.01),
vds=(0, 1, 0.01),
vsb=(0, 1, 0.1),
width=10e-6,
lengths=[500e-9, 600e-9],
simulator="ngspice",
simulator_path=None,
temp=27,
model_paths=[],
model_names={"pmos": "NMOS_VTH"},
description="gmid lookup table",
raw_spice="",
):
self.simdis = simdisc
self.vgs = np.array(vgs)
self.vds = np.array(vds)
self.vsb = np.array(vsb)
self.width = width
self.lengths = np.array(lengths)
self.simulator = simulator
self.simulator_path = simulator_path
self.temp = temp
self.model_paths = model_paths
self.model_names = model_names
self.description = description
self.raw_spice = raw_spice
self.lookup_table = {}
self.validate_paths()
self.setup_simulator()
################################################################################
def validate_paths(self):
path_list = self.model_paths[::]
if self.simulator_path is not None:
path_list.append(self.simulator_path)
for path in path_list:
if not os.path.exists(path):
continue
def setup_simulator(self):
def check_for_binary(binary_name):
if shutil.which(binary_name) is None:
raise ValueError(f"The binary '{binary_name}' is not accessible.")
if self.simulator_path is None:
check_for_binary(self.simulator)
self.simulator_path = self.simulator
################################################################################
def __make_tmp_files(self):
self.input_file_path = tempfile.NamedTemporaryFile(delete=False).name
self.log_file_path = tempfile.NamedTemporaryFile(delete=False).name
self.output_file_path = tempfile.NamedTemporaryFile(delete=False).name
def __remove_tmp_files(self):
os.remove(self.input_file_path)
os.remove(self.log_file_path)
os.remove(self.output_file_path)
################################################################################
# NGSPICE #
################################################################################
def __ngspice_parameters(self):
transistor_model = self.simdis[1:]
self.parameter_table = {
# parameter name : [name recognized by simulator, name used in the output file],
"id" : ["save i(vds)" , "i(i_vds)"],
"vth" : [f"save @{transistor_model}[vth]" , f"v(@{transistor_model}[vth])"],
"vdsat": [f"save @{transistor_model}[vdsat]", f"v(@{transistor_model}[vdsat])"],
"gm" : [f"save @{transistor_model}[gm]" , f"@{transistor_model}[gm]"],
"gmbs" : [f"save @{transistor_model}[gmbs]" , f"@{transistor_model}[gmbs]"],
"gds" : [f"save @{transistor_model}[gds]" , f"@{transistor_model}[gds]"],
"cgg" : [f"save @{transistor_model}[cgg]" , f"@{transistor_model}[cgg]"],
"cgs" : [f"save @{transistor_model}[cgs]" , f"@{transistor_model}[cgs]"],
"cbg" : [f"save @{transistor_model}[cbg]" , f"@{transistor_model}[cbg]"],
"cgd" : [f"save @{transistor_model}[cgd]" , f"@{transistor_model}[cgd]"],
"cdd" : [f"save @{transistor_model}[cdd]" , f"@{transistor_model}[cdd]"],
}
self.save_internal_parameters = "\n".join([values[0] for values in self.parameter_table.values()])
def __ngspice_simulator_setup(self):
vgs_start, vgs_stop, vgs_step = self.vgs * self.r
vds_start, vds_stop, vds_step = self.vds * self.r
analysis_string = f"dc VDS {vds_start} {vds_stop} {vds_step} VGS {vgs_start} {vgs_stop} {vgs_step}"
simulator = [
f".options TEMP = {self.temp}",
f".options TNOM = {self.temp}",
".control",
self.save_internal_parameters,
analysis_string,
"let i_vds = abs(i(vds))",
f"write {self.output_file_path} all",
".endc",
".end",
]
return simulator
def __run_ngspice(self, circuit):
with open(self.input_file_path, "w") as file:
file.write("\n".join(circuit))
ngspice_command = f"{self.simulator_path} -b -o {self.log_file_path} {self.input_file_path}"
subprocess.run(ngspice_command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def __parse_ngspice_output(self):
ars, _ = NgspiceRawFileReader().read_file(self.output_file_path)
return ars
def __save_ngspice_parameters(self, analysis, mos, length, vsb):
column_names = analysis[0].dtype.names
data = analysis[0]
for p in self.parameter_table.keys():
col_name = self.parameter_table[p][1]
if col_name in column_names:
res = np.array(data[col_name])
self.lookup_table[self.identifier][p][length][vsb] = res.reshape(self.n_vgs, self.n_vds)
################################################################################
# HSPICE #
################################################################################
def __hspice_parameters(self):
self.parameter_table = {
# parameter name : [name recognized by simulator, name used in the output file],
"id" : [".probe DC m_id = par('abs(i(vds))')" , "m_id"],
"vth" : [".probe DC m_vth = par('vth(m1)')" , "m_vth"],
"vdsat": [".probe DC m_vdsat = par('vdsat(m1)')" , "m_vdsat"],
"gm" : [".probe DC m_gm = par('gmo(m1)')" , "m_gm"],
"gmbs" : [".probe DC m_gmb = par('gmbso(m1)')" , "m_gmb"],
"gds" : [".probe DC m_gds = par('gdso(m1)')" , "m_gds"],
"cgg" : [".probe DC m_cgg = par('cggbo(m1)')" , "m_cgg"],
"cgs" : [".probe DC m_cgs = par('-cgsbo(m1)')" , "m_cgs"],
"cgd" : [".probe DC m_cgd = par('-cgdbo(m1)')" , "m_cgd"],
"cgb" : [".probe DC m_cgb = par('cggbo(m1)-(-cgsbo(m1))-(-cgdbo(m1))')", "m_cgb"],
"cdd" : [".probe DC m_cdd = par('cddbo(m1)')" , "m_cdd"],
"css" : [".probe DC m_css = par('-cgsbo(m1)-cbsbo(m1)')" , "m_css"],
}
self.save_internal_parameters = "\n".join([values[0] for values in self.parameter_table.values()])
def __hspice_simulator_setup(self):
vgs_start, vgs_stop, vgs_step = self.vgs * self.r
vds_start, vds_stop, vds_step = self.vds * self.r
analysis_string = f".dc VGS {vgs_start} {vgs_stop} {vgs_step} VDS {vds_start} {vds_stop} {vds_step}"
simulator = [
f".TEMP = {self.temp}",
".options probe dccap brief accurate",
".option POST=2",
self.save_internal_parameters,
analysis_string,
".end",
]
return simulator
def __run_hspice(self, circuit):
with open(self.input_file_path, "w") as file:
file.write("\n".join(circuit))
hspice_command = f"{self.simulator_path} -i {self.input_file_path} -o {tempfile.gettempdir()}"
subprocess.run(hspice_command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def __parse_hspice_output(self):
import_export(self.input_file_path + ".sw0", "pickle")
with open(self.input_file_path + "_sw0.pickle", 'rb') as file:
loaded_data = pickle.load(file)
return loaded_data
def __save_hspice_parameters(self, analysis, mos, length, vsb):
for p in self.parameter_table.keys():
col_name = self.parameter_table[p][1]
if col_name in analysis.keys():
res = np.array(analysis[col_name]).T
self.lookup_table[self.identifier][p][length][vsb] = res
################################################################################
# Shared Methods #
################################################################################
def __initalize(self):
self.n_lengths = len(self.lengths)
self.n_vsb = round((self.vsb[1] - self.vsb[0]) / self.vsb[2]) + 1
self.n_vds = round((self.vds[1] - self.vds[0]) / self.vds[2]) + 1
self.n_vgs = round((self.vgs[1] - self.vgs[0]) / self.vgs[2]) + 1
self.lookup_table[self.identifier] = {}
for p in self.parameter_table:
self.lookup_table[self.identifier][p] = np.zeros(shape=(self.n_lengths, self.n_vsb, self.n_vgs, self.n_vds))
# choose right simulator
if self.simulator == "ngspice":
self.__ngspice_parameters()
self.simulator_setup = self.__ngspice_simulator_setup
self.run = self.__run_ngspice
self.parse = self.__parse_ngspice_output
self.save = self.__save_ngspice_parameters
elif self.simulator == "hspice":
self.__hspice_parameters()
self.simulator_setup = self.__hspice_simulator_setup
self.run = self.__run_hspice
self.parse = self.__parse_hspice_output
self.save = self.__save_hspice_parameters
def __generate_netlist(self, length, vsb):
if self.identifier in self.model_names:
model_name = self.model_names[self.identifier]
else:
model_name = self.model_names.get("pmos")
if model_name is None:
raise ValueError(" Neither 'nmos' nor 'pmos' is avaliable in the model_name")
if self.model_paths:
include_string = "\n".join([f".lib {path}" for path in self.model_paths])
else:
include_string = ""
circuit = [
"* Lookup Table Generation *",
include_string,
"VGS NG 0 DC=0",
f"VBS NB 0 DC={-vsb * self.r}",
"VDS ND 0 DC=0",
f"XM1 ND NG 0 NB {model_name} l={length} w={self.width}",
self.raw_spice,
]
return circuit
def __generate_loopkup_table(self, mos):
self.__initalize()
for idx, length in enumerate(self.lengths):
print(f"-- length={length}")
for idy, vsb in enumerate(np.linspace(self.vsb[0], self.vsb[1], self.n_vsb)):
circuit = self.__generate_netlist(length, vsb)
simulator = self.simulator_setup()
circuit.extend(simulator)
self.run(circuit)
analysis = self.parse()
self.save(analysis, mos, idx, idy)
def __save_to_dictionary(self):
self.lookup_table["description"] = self.description
self.lookup_table["parameter_names"] = list(self.parameter_table.keys())
self.lookup_table["width"] = self.width
self.lookup_table["lengths"] = self.lengths
if "nmos" in self.model_names:
self.lookup_table["nmos"]["vgs"] = range_to_arr(self.vgs)
self.lookup_table["nmos"]["vds"] = range_to_arr(self.vds)
self.lookup_table["nmos"]["vsb"] = range_to_arr(self.vsb)
self.lookup_table["nmos"]["model_name"] = self.model_names["nmos"]
self.lookup_table["nmos"]["width"] = self.width
self.lookup_table["nmos"]["lengths"] = self.lengths
self.lookup_table["nmos"]["parameter_names"] = list(self.parameter_table.keys())
if "pmos" in self.model_names:
self.lookup_table["pmos"]["vgs"] = -range_to_arr(self.vgs)
self.lookup_table["pmos"]["vds"] = -range_to_arr(self.vds)
self.lookup_table["pmos"]["vsb"] = -range_to_arr(self.vsb)
self.lookup_table["pmos"]["model_name"] = self.model_names["pmos"]
self.lookup_table["pmos"]["width"] = self.width
self.lookup_table["pmos"]["lengths"] = self.lengths
self.lookup_table["pmos"]["parameter_names"] = list(self.parameter_table.keys())
def __print_netlist(self):
self.r = 1
self.identifier = "nmos"
circuit = self.__generate_netlist(self.lengths[0], 0)
if self.simulator == "ngspice":
self.__ngspice_parameters()
simulator = self.__ngspice_simulator_setup()
elif self.simulator == "hspice":
self.__hspice_parameters()
simulator = self.__hspice_simulator_setup()
circuit.extend(simulator)
print("---------------------------------------------------")
print("----- This is the netlist that gets simulated -----")
print("---------------------------------------------------")
print("\n".join(circuit))
print("---------------------------------------------------")
print("")
################################################################################
def build(self, filepath):
self.__make_tmp_files()
self.__print_netlist()
if "nmos" in self.model_names:
print("Generating lookup table for NMOS")
self.r = 1
self.identifier = "nmos"
self.__generate_loopkup_table(self.identifier)
if "pmos" in self.model_names:
print("Generating lookup table for PMOS")
self.r = -1
self.identifier = "pmos"
self.__generate_loopkup_table(self.identifier)
# Save results to file
print("Saving to file")
self.__save_to_dictionary()
np.save(filepath, self.lookup_table, allow_pickle=True)
# Remove tmp files
self.__remove_tmp_files()
print("Done")

View File

@ -1,706 +0,0 @@
# -----------------------------------------------------------------------------#
# Author: Mohamed Watfa
# URL: https://github.com/medwatt/
# -----------------------------------------------------------------------------#
import numpy as np
from scipy.interpolate import interpn, griddata
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.ticker import EngFormatter
from matplotlib.widgets import Cursor
# don't warn user about bad divisions
np.seterr(divide="ignore", invalid="ignore")
################################################################################
# Helper Functions #
################################################################################
# load the generated lookup table
def load_lookup_table(path: str):
return np.load(path, allow_pickle=True).tolist()
# tile one array so that it has the same shape as the other
def tile_arrays(A, B):
if A.ndim == 1 and B.ndim == 2:
if A.shape[0] == B.shape[0]:
return np.tile(A, (B.shape[1], 1)).T, B
elif A.shape[0] == B.shape[1]:
return np.tile(A, (B.shape[0], 1)), B
elif B.ndim == 1 and A.ndim == 2:
if B.shape[0] == A.shape[0]:
return A, np.tile(B, (A.shape[1], 1)).T
elif B.shape[0] == A.shape[1]:
return A, np.tile(B, (A.shape[0], 1))
return A, B
################################################################################
# Override Plot Settings During Runtime #
################################################################################
FIG_SIZE = (8, 4)
LINE_WIDTH = 1.5
GRID_COLOR = "0.9"
PLOT_SETTINGS = {
"FIG_SIZE": FIG_SIZE,
"LINE_WIDTH": LINE_WIDTH,
"GRID_COLOR": GRID_COLOR,
}
def set_plot_settings(var_name, new_value):
global FIG_SIZE, LINE_WIDTH, GRID_COLOR
if var_name in PLOT_SETTINGS:
globals()[var_name] = new_value
################################################################################
# Matplotlib Plot Interraction #
################################################################################
dots = []
annotations = []
def on_canvas_click(event, fig, ax):
if fig.canvas.toolbar.mode != "":
return
x = event.xdata
y = event.ydata
if x is None or y is None:
return
print(f"x={x}, y={y}")
(dot,) = ax.plot(x, y, "ro")
dots.append(dot)
formatter = EngFormatter()
x_eng = formatter(x)
y_eng = formatter(y)
annotation = ax.annotate(
f"({x_eng}, {y_eng})",
(x, y),
textcoords="offset points",
xytext=(0, 10),
ha="center",
)
annotations.append(annotation)
fig.canvas.draw()
def clear_annotations_and_dots(fig):
for dot in dots:
dot.remove()
for annotation in annotations:
annotation.remove()
fig.canvas.draw()
dots.clear()
annotations.clear()
################################################################################
# GMID #
################################################################################
class LoadMosfet:
def __init__( self, *, lookup_table, mos, lengths=None, vsb=None, vgs=None, vds=None, primary=None):
"""
Initialize a mosfet object.
Two of `lengths, vsb, vgs, vds` must be fixed at any time.
Args:
lookup_table (dict): dictionary of mosfet parameters
mos (str): type of mosfet: "nmos" or "pmos"
lengths (float, list, ndarray): length(s) of the mosfet
vsb (float, tuple): source-body voltage: tuple of the form (start, stop, step)
vgs (float, tuple): gate-source voltage: tuple of the form (start, stop, step)
vds (float, tuple): drain-source voltage: tuple of the form (start, stop, step)
primary (str): name of the primary sweep source
Example:
nmos = LoadMosfet(lookup_table=lookup_table, mos="nmos", vsb=0.0, vds=0.5, vgs=(0.3, 1))
"""
# extract data from lookup table
self.mos = mos
self.lookup_table = lookup_table
self.width = lookup_table[mos]["width"]
self.lengths = lookup_table[mos]["lengths"]
self.parameters = lookup_table[mos]["parameter_names"]
# extract a 2d table of the parameters
self.secondary_variable_idx, self.filtered_variables, self.extracted_table = \
self.extract_2d_table(lookup_table=self.lookup_table[self.mos], primary=primary, lengths=lengths, vsb=vsb, vgs=vgs, vds=vds)
self.lengths, self.vsb, self.vgs, self.vds = self.filtered_variables
# define commonly-used expressions to avoid typing them every time
self.__common_expressions()
self.__common_plot_methods()
def extract_2d_table(self, *, lookup_table, parameters=None, lengths=None, vsb=None, vgs=None, vds=None, primary=None):
"""
Filter the lookup table based
Args:
lookup_table (dict): dictionary of parameters of one of the mosfets
lengths (float, list, ndarray): length(s) of the mosfet
vsb (float, tuple): source-body voltage: tuple of the form (start, stop, step)
vgs (float, tuple): gate-source voltage: tuple of the form (start, stop, step)
vds (float, tuple): drain-source voltage: tuple of the form (start, stop, step)
primary (str): name of the primary sweep source
Returns:
secondary_idx: index of the secondary sweep variable in `lengths, vsb, vgs, vds`
filter_values: filtered values of `lengths, vsb, vgs, vds`
extracted_table: filtered values of parameters
"""
# check that at least two parameters are provided
params = [lengths is not None, vsb is not None, vgs is not None, vds is not None]
if sum(params) < 2:
raise ValueError("Please provide at least two parameters.")
def get_indices(var, target):
data = lookup_table[var]
if isinstance(target, tuple): # when `vsb, vgs, vds` is a range
start, end = target[:2]
indices = np.where((data >= start) & (data <= end))[0]
if len(target) == 3: # if it contains a step
step = int(target[2] / (data[1] - data[0]))
indices = indices[::step]
# lengths must be handled separately since they are provided as list of values
elif var == "lengths" and isinstance(target, (list, np.ndarray)):
# filter by lengths
mask = np.isin(self.lookup_table["lengths"], np.array(target))
indices = np.nonzero(mask)[0]
indices = np.array(indices, dtype=int)
else:
# by eliminating all float variables, we will be left with one variable
# that is not a float, and that will be the secondary variable
variables[var] = True
index = (np.abs(data - target)).argmin()
return np.array([index]), data[index]
return indices, data[indices]
secondary_idx = None
variables = {"lengths": False, "vsb": False, "vgs": False, "vds": False}
if primary:
variables[primary] = True
indices_and_values = {
"lengths": get_indices("lengths", lengths) if lengths is not None else (slice(None), lookup_table["lengths"]),
"vsb": get_indices("vsb", vsb) if vsb is not None else (slice(None), lookup_table["vsb"]),
"vgs": get_indices("vgs", vgs) if vgs is not None else (slice(None), lookup_table["vgs"]),
"vds": get_indices("vds", vds) if vds is not None else (slice(None), lookup_table["vds"]),
}
slice_indices = []
filter_values = []
for idx, item in enumerate(variables.keys()):
slice_indices.append(indices_and_values[item][0])
filter_values.append(indices_and_values[item][1])
slice_indices = tuple(slice_indices)
def slice_me(a, slices):
x = a[slices[0], :, :, :]
x = x[:, slices[1], :, :]
x = x[:, :, slices[2], :]
x = x[:, :, :, slices[3]]
return x
# extract the data based on the indices
extracted_table = {}
if not parameters:
parameters = lookup_table["parameter_names"]
for p in parameters:
if p in lookup_table:
x = np.squeeze(slice_me(lookup_table[p], slice_indices))
if x.ndim > 1 and x.shape[0] > x.shape[1]:
extracted_table[p] = x.T
else:
extracted_table[p] = x
one_key = next(iter(extracted_table))
extracted_table["width"] = np.array(self.width)
extracted_table["lengths"], _ = tile_arrays(filter_values[0], extracted_table[one_key])
extracted_table["vsb"], _ = tile_arrays(filter_values[1], extracted_table[one_key])
extracted_table["vgs"], _ = tile_arrays(filter_values[2], extracted_table[one_key])
extracted_table["vds"], _ = tile_arrays(filter_values[3], extracted_table[one_key])
if primary and secondary_idx:
secondary_idx = list(variables.values()).index(False)
return secondary_idx, filter_values, extracted_table
################################################################################
# Plotting Methods #
################################################################################
def __generate_plot_labels(self):
variables_labels = ["lengths", "vsb", "vgs", "vds"]
model_name = self.lookup_table[self.mos]["model_name"]
title_label = []
for i, v in enumerate(self.filtered_variables):
if not isinstance(v, np.ndarray):
label = variables_labels[i]
title_label.append(f"V_{{ \\mathrm{{ { (label[1:]).upper() } }} }}")
title_label.append(v)
self.plot_labels = {}
self.plot_labels["title"] = f"{model_name}, " + "$%s=%.2f$, $%s=%.2f$" % tuple(title_label)
legend_formatter = EngFormatter(unit="m")
self.plot_labels["lengths"] = [legend_formatter.format_eng(sw) for sw in self.lengths]
def __plot_settings(
self,
y: np.ndarray,
x_limit: tuple = (),
y_limit: tuple = (),
x_scale: str = "",
y_scale: str = "",
x_eng_format: bool = False,
y_eng_format: bool = False,
x_label: str = "",
y_label: str = "",
title: str = "",
save_fig: str = "",
):
fig, ax = plt.subplots(1, 1, figsize=FIG_SIZE, tight_layout=True)
fig.canvas.mpl_connect(
"button_press_event",
lambda event: on_canvas_click(event, fig, ax),
)
fig.canvas.mpl_connect(
"key_press_event",
lambda event: clear_annotations_and_dots(fig) if event.key == "d" else None,
)
ax.set_title(title)
ax.grid(True, which="both", ls="--", color=GRID_COLOR)
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
if x_limit:
ax.set_xlim(*x_limit)
if y_limit:
ax.set_ylim(*y_limit)
if x_scale:
ax.set_xscale(x_scale)
if y_scale:
ax.set_yscale(y_scale)
else:
# TODO: this might not always give expected result
if np.max(y) / np.min(y) > 1000:
ax.set_yscale("log")
# set engineering format if specified
if y_eng_format:
ax.yaxis.set_major_formatter(EngFormatter(unit=""))
if x_eng_format:
ax.xaxis.set_major_formatter(EngFormatter(unit=""))
return fig, ax
def __plot(self, x, y, fig, ax, legend, save_fig):
if isinstance(x, np.ndarray) and isinstance(y, np.ndarray) and x.ndim == y.ndim:
ax.plot(x.T, y.T, lw=LINE_WIDTH, picker=True)
elif isinstance(x, (list, tuple)) and isinstance(y, (list, tuple)):
for x_, y_ in zip(x, y):
if x_.ndim == 1 and x_.shape[0] != y_.shape[0]:
ax.plot(x_, y_.T, lw=LINE_WIDTH, picker=True)
else:
ax.plot(x_, y_, lw=LINE_WIDTH, picker=True)
elif x.ndim == 1:
if x.shape[0] != y.shape[0]:
ax.plot(x, y.T, lw=LINE_WIDTH, picker=True)
else:
ax.plot(x, y, lw=LINE_WIDTH, picker=True)
elif y.ndim == 1:
if y.shape[0] != x.shape[0]:
ax.plot(x.T, y, lw=LINE_WIDTH, picker=True)
else:
ax.plot(x, y, lw=LINE_WIDTH, picker=True)
if legend:
ax.legend(legend, loc="center left", bbox_to_anchor=(1, 0.5))
if save_fig:
ax.figure.savefig(save_fig, bbox_inches="tight")
def plot_by_expression(
self,
*,
x_expression: dict,
y_expression: dict,
lengths: tuple = (),
x_limit: tuple = (),
y_limit: tuple = (),
x_scale: str = "",
y_scale: str = "",
x_eng_format: bool = False,
y_eng_format: bool = False,
title: str = None,
save_fig: str = "",
return_result: bool = False,
):
extracted_table = self.extracted_table
# plot labels
self.__generate_plot_labels()
if title is not None:
self.plot_labels["title"] = title
# filter by lengths
mask = np.isin(self.lengths, np.array(lengths))
indices = np.nonzero(mask)[0]
length_indices = np.array(indices, dtype=int)
if length_indices.size > 0:
legend = [self.plot_labels["lengths"][i] for i in length_indices]
else:
legend = self.plot_labels["lengths"]
x, x_label = self.__calculate_from_expression(x_expression, extracted_table, length_indices)
y, y_label = self.__calculate_from_expression(y_expression, extracted_table, length_indices)
fig, ax = self.__plot_settings(
y,
x_limit,
y_limit,
x_scale,
y_scale,
x_eng_format,
y_eng_format,
x_label,
y_label,
self.plot_labels["title"],
save_fig,
)
self.__plot(x, y, fig, ax, legend, save_fig)
if return_result:
return x, y
def plot_by_sweep(
self,
*,
lengths,
vsb,
vgs,
vds,
primary,
x_expression,
y_expression,
title: str = "",
x_limit: tuple = (),
y_limit: tuple = (),
x_scale: str = "",
y_scale: str = "",
x_eng_format: bool = False,
y_eng_format: bool = False,
save_fig: str = "",
return_result: bool = False,
):
secondary_variable_idx, filtered_variables, extracted_table = self.extract_2d_table(
lookup_table=self.lookup_table[self.mos], lengths=lengths, vsb=vsb, vgs=vgs, vds=vds, primary=primary
)
x, x_label = self.__calculate_from_expression(x_expression, extracted_table)
y, y_label = self.__calculate_from_expression(y_expression, extracted_table)
fig, ax = self.__plot_settings(
y,
x_limit,
y_limit,
x_scale,
y_scale,
x_eng_format,
y_eng_format,
x_label,
y_label,
title,
save_fig,
)
if secondary_variable_idx:
legend_formatter = EngFormatter(unit="")
legend = [legend_formatter.format_eng(sw) for sw in filtered_variables[secondary_variable_idx]]
else:
legend = None
self.__plot(x, y, fig, ax, legend, save_fig)
if return_result:
return x, y
def quick_plot(
self,
x: np.ndarray | list | tuple,
y: np.ndarray | list | tuple,
*,
x_label: str = "",
y_label: str = "",
x_limit: tuple = (),
y_limit: tuple = (),
x_scale: str = "",
y_scale: str = "",
x_eng_format: bool = False,
y_eng_format: bool = False,
legend: list = [],
title: str = None,
save_fig: str = "",
):
"""
Make quick plots. As a reminder, when `x` and `y` are of size m x n, pass
them to this function as x.T and y.T
"""
fig, ax = self.__plot_settings(
y,
x_limit,
y_limit,
x_scale,
y_scale,
x_eng_format,
y_eng_format,
x_label,
y_label,
title,
save_fig,
)
self.__plot(x, y, fig, ax, legend, save_fig)
# }}}
################################################################################
# Expression Handling #
################################################################################
def __calculate_from_expression(
self,
expression: dict,
table: dict,
filter_by_rows: np.ndarray = np.array([]),
):
if isinstance(expression, dict):
var_list = []
for v in expression["variables"]:
var = table[v]
if filter_by_rows.size > 0 and var.ndim > 1:
var_list.append((np.take(var, filter_by_rows, 0)))
else:
var_list.append(var)
if "function" in expression:
values = expression["function"](*var_list)
else:
values = var_list[0]
try:
return values, expression["label"]
except KeyError:
return values, ""
else:
return expression, None
def __common_expressions(self):
# create attributes for parameters from the lookup table
LABEL_TABLE = {
"lengths": ["\\mathrm{Length}", "m"],
"vsb": ["V_{\\mathrm{SB}}", "V"],
"vgs": ["V_{\\mathrm{GS}}", "V"],
"vds": ["V_{\\mathrm{DS}}", "V"],
"id": ["I_{D}", "A"],
"vth": ["V_{\\mathrm{TH}}", "V"],
"vdsat": ["V_{\\mathrm{DS_{\\mathrm{SAT}}}}", "V"],
"gm": ["g_{m}", "S"],
"gmbs": ["g_{\\mathrm{mbs}}", "S"],
"gds": ["g_{\\mathrm{ds}}", "S"],
"cgg": ["c_{\\mathrm{gg}}", "F"],
"cgs": ["c_{\\mathrm{gs}}", "F"],
"cbg": ["c_{\\mathrm{bg}}", "F"],
"cgd": ["c_{\\mathrm{gd}}", "F"],
"cdd": ["c_{\\mathrm{dd}}", "F"],
}
for parameter, (label, unit) in LABEL_TABLE.items():
if parameter in self.parameters or parameter in ["lengths", "vsb", "vgs", "vds"]:
setattr(
self,
f"{parameter}_expression",
{"variables": [parameter], "label": f"${label}\ ({unit})$"},
)
self.gmid_expression = {
"variables": ["gm", "id"],
"function": lambda x, y: x / y,
"label": "$g_m/I_D (S/A)$",
}
self.vstar_expression = {
"variables": ["gm", "id"],
"function": lambda x, y: (2 * y) / x,
"label": "$V^{\\star} (V)$",
}
self.gain_expression = {
"variables": ["gm", "gds"],
"function": lambda x, y: x / y,
"label": "$g_{m}/g_{\\mathrm{ds}}$",
}
self.current_density_expression = {
"variables": ["id", "width"],
"function": lambda x, y: x / y,
"label": "$I_{D}/W (A/m)$",
}
self.transist_frequency_expression = {
"variables": ["gm", "cgg"],
"function": lambda x, y: x / (2 * np.pi * y),
"label": "$f_{T} (\\mathrm{Hz})$",
}
self.early_voltage_expression = {
"variables": ["id", "gds"],
"function": lambda x, y: x / y,
"label": "$V_{A} (V)$",
}
self.rds_expression = {
"variables": ["gds"],
"function": lambda x: 1 / x,
"label": "$r_{\\mathrm{ds}} (\\Omega)$",
}
def __common_plot_methods(self):
PLOT_METHODS = {
"current_density_plot": [self.gmid_expression, self.current_density_expression],
"gain_plot": [self.gmid_expression, self.gain_expression],
"transit_frequency_plot": [self.gmid_expression, self.transist_frequency_expression],
"early_voltage_plot": [self.gmid_expression, self.early_voltage_expression],
}
# This is not ideal since I need to keep track of the signature of `plot_by_expression`.
# I can use `partial` from `functools` here, but it doesn't hide the bound variables, which I don't like.
def create_plot_method(self, x_expression, y_expression):
def plot_method(
self,
lengths: tuple = (),
x_limit: tuple = (),
y_limit: tuple = (),
x_scale: str = "",
y_scale: str = "",
x_eng_format: bool = False,
y_eng_format: bool = False,
title: str = None,
save_fig: str = "",
return_result: bool = False,
):
return self.plot_by_expression(
x_expression=x_expression,
y_expression=y_expression,
lengths=lengths,
x_scale=x_scale,
y_scale=y_scale,
x_limit=x_limit,
y_limit=y_limit,
x_eng_format=x_eng_format,
y_eng_format=y_eng_format,
title=title,
save_fig=save_fig,
return_result=return_result,
)
return plot_method
for method_name, (x, y) in PLOT_METHODS.items():
setattr(LoadMosfet, method_name, create_plot_method(self, x_expression=x, y_expression=y))
################################################################################
# Lookup Methods #
################################################################################
def interpolate(self, *, x_expression, x_value, y_expression, y_value, z_expression):
"""
Given (1) a value from x_expression,
(2) a value from y_expression,
find value of z_expression using interpolation.
Args:
x_expression (dict): expression of how to calculate the points on the x-axis
x_value (float, dict): value(s) inside the domain of x_expression
y_expression (dict): expression of how to calculate the points on the y-axis
y_value (float, dict): value(s) inside the domain of y_expression
z_expression (dict): expression of how to calculate the value you're looking for
Returns:
value of expression you're looking for
Example:
x = nmos.interpolate(
x_expression=nmos.vgs_expression,
x_value=0.65,
y_expression=nmos.gmid_expression,
y_value=15,
z_expression=nmos.lengths_expression,
)
"""
x_array, _ = self.__calculate_from_expression(x_expression, self.extracted_table)
y_aray, _ = self.__calculate_from_expression(y_expression, self.extracted_table)
z_array, _ = self.__calculate_from_expression(z_expression, self.extracted_table)
points = np.column_stack((x_array.ravel(), y_aray.ravel()))
if isinstance(x_value, (tuple, np.ndarray)) and isinstance(y_value, (int, float)):
if isinstance(x_value, tuple):
x = np.arange(*x_value)
else:
x = x_value
evaluate_at = np.column_stack((x, np.full(x.shape, y_value)))
elif isinstance(y_value, (tuple, np.ndarray)) and isinstance(x_value, (int, float)):
if isinstance(y_value, tuple):
y = np.arange(*y_value)
else:
y = y_value
evaluate_at = np.column_stack((np.full(y.shape, x_value), y))
elif isinstance(x_value, tuple) and isinstance(y_value, tuple):
x = np.arange(*x_value)
y = np.arange(*y_value)
X, Y = np.meshgrid(x, y)
evaluate_at = np.dstack((X, Y)).transpose(1, 0, 2)
else:
evaluate_at = np.array([x_value, y_value])
z_value = griddata(points, z_array.ravel(), evaluate_at, method='cubic', rescale=True)
return z_value
def lookup_expression_from_table(self, *, lengths, vsb, vgs, vds, primary, expression):
"""
Calculate a parameter using the entire table.
No interpolation is used.
Args:
lengths (float, list, ndarray): length(s) of the mosfet
vsb (float, tuple): source-body voltage, tuple of the form (start, stop, step)
vgs (float, tuple): gate-source voltage, tuple of the form (start, stop, step)
vds (float, tuple): drain-source voltage, tuple of the form (start, stop, step)
primary (str): name of the primary sweep source: "lengths", "vsb", "vgs", or "vds"
expression(dict): expression of how to calculate the value you're looking for
Example:
x = nmos.lookup_expression_from_table(
lengths=100e-9,
vsb=0,
vds=(0.0, 1, 0.01),
vgs=(0.0, 1.01, 0.2),
primary="vds",
expression=nmos.current_density_expression,
)
"""
parameters = expression["variables"].copy()
remove_from_parameters = ["width", "length"]
for item in remove_from_parameters:
if item in parameters:
parameters.remove(item)
_, _, extracted_table = self.extract_2d_table(lookup_table=self.lookup_table[self.mos], parameters=parameters, lengths=lengths, vsb=vsb, vgs=vgs, vds=vds, primary=primary)
x, _ = self.__calculate_from_expression(expression, extracted_table)
return x

View File

@ -1,140 +0,0 @@
from pick import pick
def description_menu(model):
Options_1 = ["Corner lib typical", "Corner lib fast", "Corner lib slow", "Exit"]
title_1 = "Please choose the corner lib: "
discription = input("Please enter a discription: ")
simulator = "ngspice"
_, index = pick(Options_1, title_1)
if index == 0:
print("You have chosen Corner lib typical")
print(model)
if any('lv' in value for value in model.values()):
model_path = ["cornerMOSlv.lib mos_tt",]
else:
model_path = ["cornerMOShv.lib mos_tt",]
elif index == 1:
print("You have chosen Corner lib fast")
if any('lv' in value for value in model.values()):
model_path = ["cornerMOSlv.lib mos_ff",]
else:
model_path = ["cornerMOShv.lib mos_ff",]
elif index == 2:
print("You have chosen Corner lib slow")
if any('lv' in value for value in model.values()):
model_path = ["cornerMOSlv.lib mos_ss",]
else:
model_path = ["cornerMOShv.lib mos_ss",]
elif index == 3:
print("You have chosen to exit")
exit()
return discription, simulator, model_path
def sweeping_menu():
def get_floats(prompt):
while True:
user_input = input(prompt)
if user_input.lower() == 'exit':
return 'exit'
try:
return tuple(map(float, user_input.split(',')))
except ValueError:
print("Invalid input format. Please enter three numbers separated by commas (e.g., 0, 1, 0.01).")
# Prompt for VSB
while True:
vsb = get_floats("Enter VSB (start, stop, step) or type 'exit' to quit: ")
if vsb == 'exit':
return 'Exited'
elif len(vsb) == 3:
break
# Prompt for VGS
while True:
vgs = get_floats("Enter VGS (start, stop, step) or type 'exit' to quit: ")
if vgs == 'exit':
return 'Exited'
elif len(vgs) == 3:
break
# Prompt for VDS
while True:
vds = get_floats("Enter VDS (start, stop, step) or type 'exit' to quit: ")
if vds == 'exit':
return 'Exited'
elif len(vds) == 3:
break
# Prompt for width
while True:
width_input = input("Enter width value (e.g., 10e-6) or type 'exit' to quit: ")
if width_input.lower() == 'exit':
return 'Exited'
try:
width = float(width_input)
break
except ValueError:
print("Invalid width input. Please enter a valid number (e.g., 10e-6).")
# Prompt for lengths or auto-generate
while True:
lengths_input = input("Enter lengths (comma-separated, e.g., 500e-9, 600e-9), or type 'auto' for auto-generation: ")
if lengths_input.lower() == 'exit':
return 'Exited'
elif lengths_input.lower() == 'auto':
try:
start = float(input("Enter start value for lengths: "))
increment = float(input("Enter increment value: "))
count = int(input("Enter number of increments: "))
lengths = [start + i * increment for i in range(count)]
break
except ValueError:
print("Invalid input for auto-generation. Try again.")
else:
try:
lengths = [float(i) for i in lengths_input.split(',')]
break
except ValueError:
print("Invalid lengths input. Please enter comma-separated numbers.")
return vsb, vgs, vds, width, lengths
def input_selection():
# Simply return "ihp_open_pdk" without asking for input
return "ihp_open_pdk"
def transistor_menu():
Options = ["NMOS_LV", "PMOS_LV", "NMOS_HV", "PMOS_HV", "Exit"]
title = "Please choose the transistor model: "
option, index = pick(Options, title)
if index == 0:
print("You have chosen NMOS_LV")
model = {"nmos": "sg13_lv_nmos"}
simulation_description = "*n.xm1.nsg13_lv_nmos"
return model, simulation_description
elif index == 1:
print("You have chosen PMOS_LV")
model = {"pmos": "sg13_lv_pmos"}
simulation_description = "*n.xm1.nsg13_lv_pmos"
return model, simulation_description
elif index == 2:
print("You have chosen NMOS_HV")
model = {"nmos": "sg13_hv_nmos"}
simulation_description = "*n.xm1.nsg13_hv_nmos"
return model, simulation_description
elif index == 3:
print("You have chosen PMOS_HV")
model = {"pmos": "sg13_hv_pmos"}
simulation_description = "*n.xm1.nsg13_hv_pmos"
return model, simulation_description
elif index == 4:
print("You have chosen to exit")
exit()
return model, simulation_description

View File

@ -1,14 +0,0 @@
from setuptools import setup
setup(
name='mosplot',
version='0.1.0',
description='A python tool for making plots of mosfet parameters.',
author='Mohamed Watfa',
author_email='medwatt@hotmail.com',
install_requires=[
'numpy',
'matplotlib',
'scipy'
],
)

View File

@ -2,39 +2,14 @@
"cells": [
{
"cell_type": "code",
"execution_count": 8,
"id": "fe26dc38-6623-48db-820a-cf131ac9a268",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Successfully imported from src.main!\n"
]
}
],
"source": [
"import os\n",
"import sys\n",
"sys.path.append(os.path.abspath('../gmid_repo/mosplot/src'))\n",
"\n",
"try:\n",
" from main import load_lookup_table, LoadMosfet\n",
" print(\"Successfully imported from src.main!\")\n",
"except ImportError as e:\n",
" print(f\"Error importing directly from src.main: {e}\")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 101,
"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",
@ -43,33 +18,52 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 102,
"id": "b5b31aca-47bf-4461-8e50-16c20f03b337",
"metadata": {},
"outputs": [],
"source": [
"nmos_lv_path = '../gmid_repo/LUTs/nmos_lv_lut_tt.npy'\n",
"pmos_lv_path = '../gmid_repo/LUTs/pmos_lv_lut_tt.npy'\n",
"\n",
"\n",
"lookup_table_nmos = load_lookup_table(nmos_lv_path)\n",
"lookup_table_pmos = load_lookup_table(pmos_lv_path)"
"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": 11,
"execution_count": 103,
"id": "a03cd944-2432-457c-9b88-486ab781fde6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"dict_keys(['sg13_lv_nmos ', 'sg13_lv_pmos', 'description', 'simulator', 'parameter_names', 'device_parameters'])\n"
]
}
],
"source": [
"print(lookup_table.keys())"
]
},
{
"cell_type": "code",
"execution_count": 108,
"id": "743dc381-0d35-4aa9-847c-c42c80c17786",
"metadata": {},
"outputs": [],
"source": [
"nmos = LoadMosfet(lookup_table=lookup_table_nmos, mos=\"nmos\", vsb=0.0, vds=0.6)\n",
"pmos = LoadMosfet(lookup_table=lookup_table_pmos, mos=\"pmos\", vsb=0.0, vds=-0.6, vgs=(-1.2, -0.15))\n"
"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))"
]
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 109,
"id": "b27d5fca-3436-4df7-895f-f6a4bbd7a80d",
"metadata": {
"jupyter": {
@ -263,19 +257,19 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 110,
"id": "b7cc630f-b385-47a6-a6f9-ac0d10effffe",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "29f64ef5153445daad7e300c3c91e7f1",
"model_id": "f4bd29dd4a254e499496ceb2f8444c8f",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.20 μm', '…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…"
]
},
"metadata": {},
@ -283,13 +277,13 @@
}
],
"source": [
"width_values = nmos.extracted_table['width']\n",
"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(gm_values/id_values, gm_values/gds_values, vgs_values, nmos.extracted_table['lengths'], 'gm/id', 'gm/gds')"
"plot_data_vs_data(gm_values/id_values, gm_values/gds_values, vgs_values, reshaped_lengths_nmos, 'gm/id', 'gm/gds')"
]
},
{
@ -302,19 +296,19 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 111,
"id": "3727c42d-a4bf-4eb0-bc11-6e859ae41324",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "9d62db56b7f240f9bd379086729554c4",
"model_id": "25e14d69e1084f71a4f21a94fe991a02",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.20 μm', '…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…"
]
},
"metadata": {},
@ -322,38 +316,14 @@
}
],
"source": [
"width_values = pmos.extracted_table['width']\n",
"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, pmos.extracted_table['lengths'], 'gm/id', 'gm/gds')"
"plot_data_vs_data(gm_values/id_values, gm_values/gds_values, vgs_values, reshaped_lengths_pmos, 'gm/id', 'gm/gds')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "efd31595-ac9a-4bcd-aa34-8ed796fb0eb6",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "5707b58d-d36f-44c6-af55-238127aea52d",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "fcc3617a-3c04-448c-a988-c30ea2603d7f",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {

View File

@ -0,0 +1,58 @@
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.
ngspice = NgspiceSimulator(
# Provide path to simulator if simulator is not in system path.
simulator_path="ngspice",
# Default simulation temperature. Override if needed.
temperature=27,
# All parameters are saved by default. Override if needed.
parameters_to_save=["id", "vth", "vdsat", "gm","gds","vgs"],
# Files to include with `.LIB`.
lib_mappings = [
("/home/pedersen/IHP-Open-PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib", " mos_tt")
],
# If the transistor is defined inside a subcircuit in
# the library files, you must specify the symbol used (first entry)
# and the hierarchical name (second entry). Override if needed.
mos_spice_symbols = ("XM1", "n.xm1.nsg13_lv_nmos"),
# Specify the width. For devices that do not take a width,
# you can specify other parameters such as the number of fingers.
# The keys are exactly those recognized by the model.
device_parameters = {
"w": 10e-6,
}
)
nmos_sweep = TransistorSweep(
mos_type="nmos",
vgs=(0, 1.2, 0.01),
vds=(0, 1.2, 0.01),
vbs=(0, -1.2, -0.1),
length = [130e-9, 260e-9, 390e-9, 520e-9, 650e-9, 780e-9, 910e-9, 1040e-9, 1170e-9, 1300e-9, 1430e-9, 1560e-9, 1690e-9, 1820e-9, 1950e-9, 2080e-9, 2210e-9, 2340e-9, 2470e-9, 2600e-9, 2730e-9, 2860e-9, 2990e-9, 3120e-9, 3250e-9, 3380e-9, 3510e-9, 3640e-9, 3770e-9, 3900e-9, 4030e-9, 4160e-9, 4290e-9, 4420e-9, 4550e-9, 4680e-9, 4810e-9, 4940e-9, 5070e-9, 5200e-9, 5330e-9, 5460e-9, 5590e-9, 5720e-9, 5850e-9, 5980e-9, 6110e-9, 6240e-9, 6370e-9, 6500e-9, 6630e-9, 6760e-9, 6890e-9, 7020e-9, 7150e-9, 7280e-9, 7410e-9, 7540e-9, 7670e-9, 7800e-9, 7930e-9, 8060e-9, 8190e-9, 8320e-9, 8450e-9, 8580e-9, 8710e-9, 8840e-9, 8970e-9, 9100e-9, 9230e-9, 9360e-9, 9490e-9, 9620e-9, 9750e-9, 9880e-9]
)
obj = LookupTableGenerator(
description="sg13_nmos_lv",
simulator=ngspice,
model_sweeps={
"sg13_lv_nmos ": nmos_sweep,
},
n_process=2,
)
# obj.op_simulation()
obj.build("./sg13_nmos_lv_LUT")

View File

@ -0,0 +1,58 @@
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.
ngspice = NgspiceSimulator(
# Provide path to simulator if simulator is not in system path.
simulator_path="ngspice",
# Default simulation temperature. Override if needed.
temperature=27,
# All parameters are saved by default. Override if needed.
parameters_to_save=["id", "vth", "vdsat", "gm","gds","vgs"],
# Files to include with `.LIB`.
lib_mappings = [
("/home/pedersen/IHP-Open-PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib", " mos_tt")
],
# If the transistor is defined inside a subcircuit in
# the library files, you must specify the symbol used (first entry)
# and the hierarchical name (second entry). Override if needed.
mos_spice_symbols = ("XM1", "n.xm1.nsg13_lv_pmos"),
# Specify the width. For devices that do not take a width,
# you can specify other parameters such as the number of fingers.
# The keys are exactly those recognized by the model.
device_parameters = {
"w": 10e-6,
}
)
# Define a sweep object for PMOS transistors.
pmos_sweep = TransistorSweep(
mos_type="pmos",
vgs=(0, -1.2, -0.01),
vds=(0, -1.2, -0.01),
vbs=(0, 1.2, 0.1),
length = [130e-9, 260e-9, 390e-9, 520e-9, 650e-9, 780e-9, 910e-9, 1040e-9, 1170e-9, 1300e-9, 1430e-9, 1560e-9, 1690e-9, 1820e-9, 1950e-9, 2080e-9, 2210e-9, 2340e-9, 2470e-9, 2600e-9, 2730e-9, 2860e-9, 2990e-9, 3120e-9, 3250e-9, 3380e-9, 3510e-9, 3640e-9, 3770e-9, 3900e-9, 4030e-9, 4160e-9, 4290e-9, 4420e-9, 4550e-9, 4680e-9, 4810e-9, 4940e-9, 5070e-9, 5200e-9, 5330e-9, 5460e-9, 5590e-9, 5720e-9, 5850e-9, 5980e-9, 6110e-9, 6240e-9, 6370e-9, 6500e-9, 6630e-9, 6760e-9, 6890e-9, 7020e-9, 7150e-9, 7280e-9, 7410e-9, 7540e-9, 7670e-9, 7800e-9, 7930e-9, 8060e-9, 8190e-9, 8320e-9, 8450e-9, 8580e-9, 8710e-9, 8840e-9, 8970e-9, 9100e-9, 9230e-9, 9360e-9, 9490e-9, 9620e-9, 9750e-9, 9880e-9]
)
obj = LookupTableGenerator(
description="sg13_omos_lv",
simulator=ngspice,
model_sweeps={
"sg13_lv_pmos" : pmos_sweep,
},
n_process=2,
)
# obj.op_simulation()
obj.build("./sg13_pmos_lv_LUT")

View File

@ -2,37 +2,13 @@
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Successfully imported from src.main!\n"
]
}
],
"source": [
"import os\n",
"import sys\n",
"sys.path.append(os.path.abspath('../../../module_0_foundations/gmid_repo/mosplot/src'))\n",
"\n",
"try:\n",
" from main import load_lookup_table, LoadMosfet\n",
" print(\"Successfully imported from src.main!\")\n",
"except ImportError as e:\n",
" print(f\"Error importing directly from src.main: {e}\")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 19,
"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",
@ -41,31 +17,54 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"nmos_lv_path = '../../../module_0_foundations/gmid_repo/LUTs/nmos_lv_lut_tt.npy'\n",
"pmos_lv_path = '../../../module_0_foundations/gmid_repo/LUTs/pmos_lv_lut_tt.npy'\n",
"lookup_table_nmos = load_lookup_table('../../../module_0_foundations/sg13_nmos_lv_LUT.npz')\n",
"lookup_table_pmos = load_lookup_table('../../../module_0_foundations/sg13_pmos_lv_LUT.npz')"
]
},
{
"cell_type": "code",
"execution_count": 22,
"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": 23,
"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",
"lookup_table_pmos = load_lookup_table(pmos_lv_path)\n",
"lookup_table_nmos = load_lookup_table(nmos_lv_path)"
"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))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"nmos = LoadMosfet(lookup_table=lookup_table_nmos, mos=\"nmos\", vsb=0.0, vds=0.6)\n",
"pmos = LoadMosfet(lookup_table=lookup_table_pmos, mos=\"pmos\", vsb=-0.2, vds=-0.6, vgs=(-1.2, -0.1))"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"execution_count": 24,
"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",
@ -265,7 +264,7 @@
},
{
"cell_type": "code",
"execution_count": 27,
"execution_count": 25,
"metadata": {},
"outputs": [],
"source": [
@ -279,7 +278,7 @@
},
{
"cell_type": "code",
"execution_count": 28,
"execution_count": 26,
"metadata": {},
"outputs": [],
"source": [
@ -289,18 +288,18 @@
},
{
"cell_type": "code",
"execution_count": 29,
"execution_count": 34,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "c19c949de81145fbb8f56ff8c2747ab1",
"model_id": "ea4f549168444a6da3039c78cb0ef5f0",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.20 μm', '…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…"
]
},
"metadata": {},
@ -308,19 +307,22 @@
}
],
"source": [
"nmos_M6 = LoadMosfet(lookup_table=lookup_table_nmos, mos=\"nmos\", vsb=0, vds=0.6)\n",
"width_values_M6 = nmos_M6.extracted_table['width']\n",
"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",
"\n",
"width_values_M6 = nmos_M6.width\n",
"id_values_M6 = nmos_M6.extracted_table['id']\n",
"gm_values_M6 = nmos_M6.extracted_table['gm']\n",
"gds_values_M6 = nmos_M6.extracted_table['gds']\n",
"vgs_values_M6 = nmos_M6.extracted_table['vgs']\n",
"\n",
"plot_data_vs_data(gm_values_M6/id_values_M6, gm_values_M6/gds_values_M6, vgs_values_M6, nmos_M6.extracted_table['lengths'], 'gm/id', 'gm/gds')"
"plot_data_vs_data(gm_values_M6/id_values_M6, gm_values_M6/gds_values_M6, vgs_values_M6, reshaped_lengths_nmos_M6, 'gm/id', 'gm/gds')"
]
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 13,
"metadata": {},
"outputs": [
{
@ -329,7 +331,7 @@
"7.949913192125772e-05"
]
},
"execution_count": 9,
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
@ -346,7 +348,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 14,
"metadata": {},
"outputs": [
{
@ -369,18 +371,18 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 38,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "1fec87aafa2d4d009fc5065b0c919616",
"model_id": "c13371af34b44b4fbcd6d05b532d361f",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.25 μm', '0.30 μm', '0…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…"
]
},
"metadata": {},
@ -388,19 +390,24 @@
}
],
"source": [
"pmos_M7 = LoadMosfet(lookup_table=lookup_table_pmos, mos=\"pmos\", vsb=0, vds=-0.6, vgs=(-1.2, -0.1))\n",
"width_values_M7 = pmos_M7.extracted_table['width']\n",
"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",
"\n",
"width_values_M7 = pmos_M7.width\n",
"id_values_M7 = pmos_M7.extracted_table['id']\n",
"gm_values_M7 = pmos_M7.extracted_table['gm']\n",
"gds_values_M7 = pmos_M7.extracted_table['gds']\n",
"vgs_values_M7 = pmos_M7.extracted_table['vgs']\n",
"\n",
"plot_data_vs_data(gm_values_M7/id_values_M7, gm_values_M7/gds_values_M7, vgs_values_M7, pmos_M7.extracted_table['lengths'], 'gm/id', 'gds')\n"
"plot_data_vs_data(gm_values_M7/id_values_M7, gm_values_M7/gds_values_M7, vgs_values_M7, reshaped_lengths_pmos_M7, 'gm/id', 'gds')\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 39,
"metadata": {},
"outputs": [],
"source": [
@ -411,7 +418,7 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 40,
"metadata": {},
"outputs": [
{
@ -434,18 +441,18 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 41,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "d9ae4f5537e44eb98cb8f43a99ace9e6",
"model_id": "2eca17df295841629e74fb72e1cff63f",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.30 μm', '0.40 μm', '0…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…"
]
},
"metadata": {},
@ -454,12 +461,12 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "86826b2d41334c9f97fc4c71228b844b",
"model_id": "88996c8fb302472b8fb5add0523f26dc",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.25 μm', '0.30 μm', '0…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…"
]
},
"metadata": {},
@ -467,13 +474,13 @@
}
],
"source": [
"plot_data_vs_data(gm_values_M6/id_values_M6,id_values_M6/width_values_M6, vgs_values_M6, nmos_M6.extracted_table['lengths'], 'gm/id', 'M6 id/W', log=True)\n",
"plot_data_vs_data(gm_values_M7/id_values_M7, id_values_M7/width_values_M7,vgs_values_M7, pmos_M7.extracted_table['lengths'], 'gm/id', 'M7 id/W', log=True)"
"plot_data_vs_data(gm_values_M6/id_values_M6,id_values_M6/width_values_M6, vgs_values_M6, reshaped_lengths_nmos_M6, 'gm/id', 'M6 id/W', log=True)\n",
"plot_data_vs_data(gm_values_M7/id_values_M7, id_values_M7/width_values_M7,vgs_values_M7, reshaped_lengths_pmos_M7, 'gm/id', 'M7 id/W', log=True)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"execution_count": 42,
"metadata": {},
"outputs": [
{
@ -543,7 +550,7 @@
},
{
"cell_type": "code",
"execution_count": 16,
"execution_count": 43,
"metadata": {},
"outputs": [
{
@ -552,7 +559,7 @@
"2.0004294796936965e-06"
]
},
"execution_count": 16,
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
@ -570,7 +577,7 @@
},
{
"cell_type": "code",
"execution_count": 17,
"execution_count": 44,
"metadata": {},
"outputs": [
{
@ -590,7 +597,7 @@
},
{
"cell_type": "code",
"execution_count": 18,
"execution_count": 45,
"metadata": {},
"outputs": [
{
@ -618,18 +625,18 @@
},
{
"cell_type": "code",
"execution_count": 19,
"execution_count": 47,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "653d5df7f39548198c9e9ad5b226e0ed",
"model_id": "5d7b576ad312473eaeab532b93f3e92f",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.25 μm', '0.30 μm', '0…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…"
]
},
"metadata": {},
@ -638,12 +645,12 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "45eaee19c87c4d629180ab7b63eae94a",
"model_id": "e116eacdd44e4571b3f183da0118a4fa",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.30 μm', '0.40 μm', '0…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…"
]
},
"metadata": {},
@ -651,8 +658,13 @@
}
],
"source": [
"pmos_M12 = LoadMosfet(lookup_table=lookup_table_pmos, mos=\"pmos\", vsb=-0.2, vds=-0.6, vgs=(-1.2, -0.1))\n",
"nmos_M34 = LoadMosfet(lookup_table=lookup_table_nmos, mos=\"nmos\", vsb=0, vds=0.6)\n",
"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",
"\n",
"id_values_M12 = pmos_M12.extracted_table['id']\n",
"gm_values_M12 = pmos_M12.extracted_table['gm']\n",
@ -664,13 +676,13 @@
"gds_values_M34 = nmos_M34.extracted_table['gds']\n",
"vgs_values_M34 = nmos_M34.extracted_table['vgs']\n",
"\n",
"plot_data_vs_data(gm_values_M12/id_values_M12, gm_values_M12/gds_values_M12, vgs_values_M12, pmos_M12.extracted_table['lengths'], 'gm/id', 'm12 gm/gds')\n",
"plot_data_vs_data(gm_values_M34/id_values_M34, gm_values_M34/gds_values_M34, vgs_values_M34, nmos_M34.extracted_table['lengths'], 'gm/id', 'm34 gm/gds')"
"plot_data_vs_data(gm_values_M12/id_values_M12, gm_values_M12/gds_values_M12, vgs_values_M12, reshaped_lengths_pmos_M12, 'gm/id', 'm12 gm/gds')\n",
"plot_data_vs_data(gm_values_M34/id_values_M34, gm_values_M34/gds_values_M34, vgs_values_M34, reshaped_lengths_nmos_M34, 'gm/id', 'm34 gm/gds')"
]
},
{
"cell_type": "code",
"execution_count": 20,
"execution_count": 48,
"metadata": {},
"outputs": [
{
@ -697,18 +709,18 @@
},
{
"cell_type": "code",
"execution_count": 21,
"execution_count": 50,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "777232b20ad64f47aaa584c273a69fe7",
"model_id": "4cca567128c14cb68100041e1a57c919",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.25 μm', '0.30 μm', '0…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…"
]
},
"metadata": {},
@ -717,12 +729,12 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "7e6503685d4f4eadb9a22f5e2fd505c1",
"model_id": "6c92cedcecfb47f491e2b860b0c65a31",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.30 μm', '0.40 μm', '0…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.13 μm', '…"
]
},
"metadata": {},
@ -730,16 +742,16 @@
}
],
"source": [
"width_values_M12 = pmos_M12.extracted_table['width']\n",
"width_values_M34 = nmos_M34.extracted_table['width']\n",
"width_values_M12 = pmos_M12.width\n",
"width_values_M34 = nmos_M34.width\n",
"\n",
"plot_data_vs_data(gm_values_M12/id_values_M12,id_values_M12/width_values_M12, vgs_values_M12, pmos_M12.extracted_table['lengths'], 'gm/id', 'M12 id/W', log=True)\n",
"plot_data_vs_data(gm_values_M34/id_values_M34, id_values_M34/width_values_M34,vgs_values_M34, nmos_M34.extracted_table['lengths'], 'gm/id', 'M34 id/W', log=True)\n"
"plot_data_vs_data(gm_values_M12/id_values_M12,id_values_M12/width_values_M12, vgs_values_M12, reshaped_lengths_pmos_M12, 'gm/id', 'M12 id/W', log=True)\n",
"plot_data_vs_data(gm_values_M34/id_values_M34, id_values_M34/width_values_M34,vgs_values_M34, reshaped_lengths_nmos_M34, 'gm/id', 'M34 id/W', log=True)\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"execution_count": 51,
"metadata": {},
"outputs": [
{
@ -792,7 +804,7 @@
},
{
"cell_type": "code",
"execution_count": 23,
"execution_count": 52,
"metadata": {},
"outputs": [
{
@ -894,6 +906,13 @@
"\"\"\"\n",
"print(summary)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {

View File

@ -12,7 +12,7 @@ ypos2=2
divy=5
subdivy=4
unity=1
x1=-1.75
x1=-0.22184875
divx=5
subdivx=8
@ -38,16 +38,16 @@ y2=33.964
y1=-136.006
color=4
node=ph(vout)
x2=5.25}
x2=-0.22184875}
B 2 680 -1295 1480 -895 {flags=graph
y1=33
y2=74
y1=27
y2=71
ypos1=0
ypos2=2
divy=5
subdivy=1
unity=1
x1=-1.75
x1=-0.22184875
divx=5
subdivx=8
@ -59,7 +59,7 @@ dataset=-1
unitx=1
logx=1
logy=0
x2=5.25}
x2=-0.22184875}
B 2 1535 -1295 2335 -895 {flags=graph
y1=37
y2=86
@ -68,7 +68,7 @@ ypos2=2
divy=5
subdivy=1
unity=1
x1=-1.75
x1=-0.22184875
divx=5
subdivx=8
@ -80,18 +80,18 @@ dataset=-1
unitx=1
logx=1
logy=0
x2=5.25
x2=-0.22184875
color=4
node=cmrr}
B 2 1525 -875 2325 -475 {flags=graph
y1=0.05
y2=28
y1=0.055
y2=30
ypos1=0
ypos2=2
divy=5
subdivy=1
unity=1
x1=-1.75
x1=-0.22184875
divx=5
subdivx=8
@ -103,7 +103,7 @@ dataset=-1
unitx=1
logx=1
logy=0
x2=5.25
x2=-0.22184875
color=4
node=psrr}
N 775 -265 775 -235 {
@ -235,7 +235,7 @@ footprint=1206
device="ceramic capacitor"}
C {gnd.sym} 530 -335 0 0 {name=l5 lab=GND}
C {iopin.sym} 620 -410 0 0 {name=p7 lab=vout}
C {devices/code_shown.sym} -445 -290 0 0 {name=MODEL only_toplevel=false
C {devices/code_shown.sym} -415 -290 0 0 {name=MODEL only_toplevel=false
format="tcleval( @value )"
value="
.lib $::SG13G2_MODELS/cornerCAP.lib cap_typ