change gmid content
|
|
@ -13,4 +13,5 @@ modules/module_3_8_bit_SAR_ADC/part_2_digital_comps/bootstrap_switch/python/
|
|||
*.so
|
||||
*.out
|
||||
*.vcd
|
||||
*.npz
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
```
|
||||
|
||||

|
||||
|
||||
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]
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
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"
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 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)$"
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
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)$"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Looking Up Values
|
||||
|
||||
While having plots is a good way to visualize trends, we might also just be
|
||||
interested in the raw value.
|
||||
|
||||

|
||||
|
||||
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',
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 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$",
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
# 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.
|
||||
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
|
@ -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()
|
||||
|
||||
|
|
@ -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}
|
||||
|
|
@ -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']
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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")
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
],
|
||||
)
|
||||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||