This commit is contained in:
PhillipRambo 2025-03-28 15:15:16 +01:00
parent 4deb3a3aa4
commit 95190cc4d5
53 changed files with 25067 additions and 1832 deletions

View File

@ -1,56 +0,0 @@
v {xschem version=3.4.5 file_version=1.2
}
G {}
K {}
V {}
S {}
E {}
N 190 -550 190 -390 {
lab=#net1}
N 190 -320 190 -300 {
lab=GND}
N 190 -360 200 -360 {
lab=GND}
N 200 -360 200 -320 {
lab=GND}
N 190 -320 200 -320 {
lab=GND}
N 190 -330 190 -320 {
lab=GND}
N 90 -480 90 -460 {
lab=GND}
N 90 -550 90 -540 {
lab=#net1}
N 90 -550 190 -550 {
lab=#net1}
N 90 -280 90 -260 {
lab=GND}
N 90 -360 90 -340 {
lab=#net2}
N 90 -360 150 -360 {
lab=#net2}
C {devices/code_shown.sym} -280 -270 0 0 {name=MODEL only_toplevel=true
format="tcleval( @value )"
value="
.lib cornerMOSlv.lib mos_tt
"}
C {devices/code_shown.sym} -290 -420 0 0 {name=NGSPICE only_toplevel=true
value="
.control
op
write output_file.raw
.endc
"}
C {sg13g2_pr/sg13_lv_nmos.sym} 170 -360 2 1 {name=M3
l=3.25u
w=3.33u
ng=1
m=1
model=sg13_lv_nmos
spiceprefix=X
}
C {devices/gnd.sym} 190 -300 0 0 {name=l2 lab=GND}
C {devices/vsource.sym} 90 -510 0 0 {name=Vdd1 value= 0.6}
C {devices/gnd.sym} 90 -460 0 0 {name=l3 lab=GND}
C {devices/vsource.sym} 90 -310 0 0 {name=Vdd2 value= 0.27}
C {devices/gnd.sym} 90 -260 0 0 {name=l4 lab=GND}

View File

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

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 80 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 91 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 61 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 42 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,27 +1,40 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "96c73c5c-d26f-4710-9dce-8b67c625438e",
"cell_type": "code",
"execution_count": 1,
"id": "fe26dc38-6623-48db-820a-cf131ac9a268",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Successfully imported from src.main!\n"
]
}
],
"source": [
"# Simple setup for using gm/id with IHP open PDK"
"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": 4,
"id": "93924bd4-4ba6-4275-8643-a10c59a209e7",
"execution_count": 2,
"id": "9f325daf-eb3b-4cf7-8ffe-b9b94e7f66ea",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"#for windows\n",
"#import sys\n",
"#sys.path.append(r'C:\\Users\\....\\gmid') # path to gmid repository\n",
"# ------\n",
"import matplotlib.pyplot as plt\n",
"from mosplot import load_lookup_table, LoadMosfet # make sure that mosplot can be found in the python path\n",
"import numpy as np\n",
"import ipywidgets as widgets\n",
"from ipywidgets import interactive\n",
"from ipywidgets import interactive_output, HBox, VBox\n",
@ -30,41 +43,34 @@
},
{
"cell_type": "code",
"execution_count": 5,
"id": "16b9f998-3107-436e-842d-c588dc4cbfee",
"execution_count": 3,
"id": "b5b31aca-47bf-4461-8e50-16c20f03b337",
"metadata": {},
"outputs": [],
"source": [
"pmos_lv_path = '/home/pedersen/projects/IHP-AnalogAcademy/modules/module_0_foundations/gmid_sweeps/pmos_lv_sweep.npy'\n",
"nmos_lv_path ='/home/pedersen/projects/IHP-AnalogAcademy/modules/module_0_foundations/gmid_sweeps/nmos_lv_sweep.npy'\n",
"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",
"lookup_table_pmos = load_lookup_table(pmos_lv_path)\n",
"lookup_table_nmos = load_lookup_table(nmos_lv_path)"
"\n",
"lookup_table_nmos = load_lookup_table(nmos_lv_path)\n",
"lookup_table_pmos = load_lookup_table(pmos_lv_path)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "1eae8452-b1d5-4611-bb86-4f2f222b2929",
"execution_count": null,
"id": "743dc381-0d35-4aa9-847c-c42c80c17786",
"metadata": {},
"outputs": [],
"source": [
"nmos = LoadMosfet(lookup_table=lookup_table_nmos, mos=\"nmos\", vsb=0.0, vds=0.4)\n",
"pmos = LoadMosfet(lookup_table=lookup_table_pmos, mos=\"pmos\", vsb=0, vds=-0.6, vgs=(-1.2, -0.1))"
]
},
{
"cell_type": "markdown",
"id": "576b86e1-caae-496f-a644-584ff4fb5dd6",
"metadata": {},
"source": [
"# Function Definitions"
"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"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "7abd7d3e-cf3d-4643-b5b3-d35f2362f7b7",
"execution_count": null,
"id": "b27d5fca-3436-4df7-895f-f6a4bbd7a80d",
"metadata": {
"jupyter": {
"source_hidden": true
@ -86,16 +92,16 @@
" unique_lengths_in_micro = unique_lengths * 1e6\n",
"\n",
" def update_plot(selected_length, x_value=None, y_value=None):\n",
" plt.figure(figsize=(8, 6)) # Ensure the plot is drawn fresh for each update\n",
" plt.figure(figsize=(12, 8)) # Make the figure wider (adjust as needed)\n",
"\n",
" if selected_length == \"Show All\":\n",
" mask = np.ones_like(length_flat, dtype=bool)\n",
" else:\n",
" selected_length_in_micro = float(selected_length.replace(' μm', ''))\n",
" tolerance = 0.1\n",
" # Recalculate the mask with matching shapes\n",
" tolerance = 0.01 # Tighten the tolerance to avoid unwanted data points\n",
" mask = np.abs(length_flat * 1e6 - selected_length_in_micro) < tolerance\n",
"\n",
" # Apply the mask to the data\n",
" x_values_for_length = x_values_flat[mask]\n",
" y_values_for_length = y_values_flat[mask] * y_multiplier\n",
" z_values_for_length = z_values_flat[mask]\n",
@ -150,30 +156,38 @@
" length_widget = widgets.Dropdown(\n",
" options=dropdown_options,\n",
" value=dropdown_options[0],\n",
" description='Select Length:',\n",
" description='Length:',\n",
" layout=widgets.Layout(width='500px') # Make the dropdown wider\n",
" )\n",
"\n",
" x_value_widget = widgets.FloatText(\n",
" value=np.mean(x_values_flat),\n",
" description=f\"Select {x_axis_name}:\",\n",
" disabled=False\n",
" description=f\"{x_axis_name}:\",\n",
" disabled=False,\n",
" layout=widgets.Layout(width='300px', margin='0 40px 0 0'), # Push input boxes more to the right\n",
" description_width='150px' # Smaller description width\n",
" )\n",
"\n",
" y_value_widget = widgets.FloatText(\n",
" value=None,\n",
" description=f\"Set {y_axis_name}:\",\n",
" disabled=True\n",
" description=f\"{y_axis_name}:\",\n",
" disabled=True,\n",
" layout=widgets.Layout(width='300px', margin='0 40px 0 0'), # Push input boxes more to the right\n",
" description_width='150px' # Smaller description width\n",
" )\n",
"\n",
" z_value_widget = widgets.FloatText(\n",
" value=None,\n",
" description=f\"Corresponding z value:\",\n",
" disabled=True\n",
" description=f\" Vgs:\",\n",
" disabled=True,\n",
" layout=widgets.Layout(width='300px', margin='0 40px 0 0'), # Push input boxes more to the right\n",
" description_width='150px' # Smaller description width\n",
" )\n",
"\n",
" select_x_or_y_widget = widgets.Checkbox(\n",
" value=True,\n",
" description=f\"Select {x_axis_name} (uncheck for {y_axis_name})\",\n",
" description=f\"{x_axis_name} (uncheck for {y_axis_name})\",\n",
" layout=widgets.Layout(width='300px') # Make the checkbox wider\n",
" )\n",
"\n",
" def toggle_x_or_y(change):\n",
@ -193,8 +207,7 @@
" })\n",
"\n",
" display(VBox([length_widget, select_x_or_y_widget, HBox([x_value_widget, y_value_widget]), z_value_widget, output]))\n",
"\n",
"\n",
" \n",
"def display_resistance(ro_value):\n",
" \"\"\"Determine the resistance value and its unit.\"\"\"\n",
" if ro_value < 1e3:\n",
@ -242,261 +255,56 @@
},
{
"cell_type": "markdown",
"id": "57bc7b38-a0e8-40c3-90c5-bd7c2bf03df4",
"id": "2fc675aa-6d59-4d74-83e2-18c56353db0d",
"metadata": {},
"source": [
"# Plotting examples\n",
"\n",
"Note: In my cases i will be using my own defined functions to plot more interatively. You can refer to the standard repository to see how to plot using the native functions in the repo..."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "cdfafca1-e7c0-4491-8a38-5785f38c8d60",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "b02c4d3ead7440c2803417b6fcc005cf",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "03c6dd08648e483ea151c95baae95aa4",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Lets start by plotting the intrinsic gain of both pmos and nmos as a function of gm/id. We start by setting the data arrays\n",
"\n",
"id_values_nmos = nmos.extracted_table['id']\n",
"gm_values_nmos = nmos.extracted_table['gm']\n",
"gds_values_nmos = nmos.extracted_table['gds']\n",
"vgs_values_nmos = nmos.extracted_table['vgs']\n",
"\n",
"id_values_pmos = pmos.extracted_table['id']\n",
"gm_values_pmos = pmos.extracted_table['gm']\n",
"gds_values_pmos = pmos.extracted_table['gds']\n",
"vgs_values_pmos = pmos.extracted_table['vgs']\n",
"\n",
"\n",
"plot_data_vs_data(gm_values_nmos/id_values_nmos, gm_values_nmos/gds_values_nmos, vgs_values_nmos, nmos.extracted_table['lengths'], 'gm/id', 'gds') # plotting nmos data\n",
"plot_data_vs_data(gm_values_pmos/id_values_pmos, gm_values_pmos/gds_values_pmos, vgs_values_pmos, pmos.extracted_table['lengths'], 'gm/id', 'gds') # plotting pmos data"
]
},
{
"cell_type": "markdown",
"id": "1cd1cfe6-8260-4b9f-8166-f953758c0563",
"metadata": {},
"source": [
"# Verifying the models\n",
"To ensure that the gmid library is working properly we will simply set the dimensions for a nmos a varify in xschem"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "7dd17dfa-abd2-4c76-9fd4-1f7824fdaa58",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.0 μA\n"
]
}
],
"source": [
"gmid = 18 #moderate inversion\n",
"id = 1e-6\n",
"gm = gmid*id\n",
"display_id, unit_id = display_current(id)\n",
"print(f'{display_id} {unit_id}')"
]
},
{
"cell_type": "markdown",
"id": "c5edba7b-f8ec-4405-a7b3-908489a7c47f",
"metadata": {},
"source": [
"# Choosing the channel length by quick overview"
]
},
{
"cell_type": "markdown",
"id": "5a570be2-c30c-456e-ad10-f18870d1d27e",
"metadata": {},
"source": [
"Here the focus is on getting a high intrinsic gain"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "7fd6ceee-3d40-460f-82d6-6d672d0d337d",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "82d7c57bbc6f44a79891a51bcd92c82e",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plot_data_vs_data(gm_values_nmos/id_values_nmos, gm_values_nmos/gds_values_nmos, vgs_values_nmos, nmos.extracted_table['lengths'], 'gm/id', 'gds') # plotting nmos data"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "3fa1a702-34dd-43de-a95e-a6654fea16eb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"R_load = 2.25 MΩ\n"
]
}
],
"source": [
"# By sweeping through the lenghts for a fixed gm/id we see that the lengths is approximatly 3.25e-6\n",
"Lnmos = 3.25e-6\n",
"gmro = 40.44\n",
"ro = gmro/gm\n",
"display_ro, unit_ro = display_resistance(ro)\n",
"print(f'R_load = {display_ro:.2f} {unit_ro}')"
]
},
{
"cell_type": "markdown",
"id": "20cf0ad2-022a-45d6-8a1f-7c41fd2e26c7",
"metadata": {},
"source": [
"# Now we want to find the Corresponding width"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "eb54a54a-491b-4c94-8d34-d14457474b25",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "9056d3790ad7459bbf57e878d655cce1",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"width_nmos = nmos.extracted_table['width']\n",
"plot_data_vs_data(gm_values_nmos/id_values_nmos, id_values_nmos/width_nmos, vgs_values_nmos, nmos.extracted_table['lengths'], 'gm/id', 'id/W', log=True) # plotting nmos data"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "e6e2b885-e999-4e29-9a07-d9183db4c021",
"metadata": {},
"outputs": [],
"source": [
"# We see that for a gm/id of 18 and length of 3.25 our, id/W is given as 0.3\n",
"id_over_width_nmos = 0.3\n",
"Wnmos = id/id_over_width_nmos"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "9ff5d012-44c7-4acb-b7e2-19ff15312594",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Width and Length for NMOS\n",
" W = 3.33 um\n",
" L = 3.25 um\n",
"Inversion Region for NMOS: Moderate Inversion\n",
"\n",
"Bias Current:\n",
" Id = 1.00μA\n",
"Parameters to check\n",
" gm/gds = 40.44\n",
" gm = 0.018 mS\n",
" ro = 2.25MΩ\n",
"\n",
"\n"
]
}
],
"source": [
"# Now we can summarize everything\n",
"\n",
"single_transistor_summary = f\"\"\"\n",
"Width and Length for NMOS\n",
" W = {Wnmos*1e6:.2f} um\n",
" L = {Lnmos*1e6:.2f} um\n",
"Inversion Region for NMOS: {determine_inversion_region(gmid, 'nmos')}\n",
"\n",
"Bias Current:\n",
" Id = {display_id:.2f}{unit_id}\n",
"Parameters to check\n",
" gm/gds = {gmro:.2f}\n",
" gm = {gm*1e3:.2} mS\n",
" ro = {display_ro:.2f}{unit_ro}\n",
"\n",
"\"\"\"\n",
"print(single_transistor_summary)"
"# NMOS GMID"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a0c63c5-99f4-46ae-a827-bb7525950798",
"id": "b7cc630f-b385-47a6-a6f9-ac0d10effffe",
"metadata": {},
"outputs": [],
"source": [
"width_values = nmos.extracted_table['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')"
]
},
{
"cell_type": "markdown",
"id": "e847c359-b57e-4e84-b0dc-93616d575efd",
"metadata": {},
"source": [
"# PMOS GMID"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3727c42d-a4bf-4eb0-bc11-6e859ae41324",
"metadata": {},
"outputs": [],
"source": [
"width_values = pmos.extracted_table['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')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "efd31595-ac9a-4bcd-aa34-8ed796fb0eb6",
"metadata": {},
"outputs": [],
"source": []
@ -504,7 +312,15 @@
{
"cell_type": "code",
"execution_count": null,
"id": "16b8b4f2-807a-4f75-8bc1-cf12a7d1ec0f",
"id": "5707b58d-d36f-44c6-af55-238127aea52d",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "fcc3617a-3c04-448c-a988-c30ea2603d7f",
"metadata": {},
"outputs": [],
"source": []

View File

@ -2,17 +2,37 @@
"cells": [
{
"cell_type": "code",
"execution_count": 122,
"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,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"#for windows\n",
"import sys\n",
"sys.path.append(r'C:\\Users\\pedersen\\gmid') # path to gmid repository\n",
"# ------\n",
"import matplotlib.pyplot as plt\n",
"from mosplot import load_lookup_table, LoadMosfet # make sure that mosplot can be found in the python path\n",
"import numpy as np\n",
"import ipywidgets as widgets\n",
"from ipywidgets import interactive\n",
"from ipywidgets import interactive_output, HBox, VBox\n",
@ -21,12 +41,12 @@
},
{
"cell_type": "code",
"execution_count": 123,
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"pmos_lv_path = r'C:\\Users\\pedersen\\Desktop\\OpenDesingCourse/no_touch_files\\gmoveridpypmos_gmid_final.npy'\n",
"nmos_lv_path = r'C:\\Users\\pedersen\\Desktop\\OpenDesingCourse/no_touch_files\\gmoveridpynmos_final_gmid.npy'\n",
"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",
"\n",
"lookup_table_pmos = load_lookup_table(pmos_lv_path)\n",
"lookup_table_nmos = load_lookup_table(nmos_lv_path)"
@ -34,7 +54,7 @@
},
{
"cell_type": "code",
"execution_count": 124,
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
@ -44,7 +64,7 @@
},
{
"cell_type": "code",
"execution_count": 125,
"execution_count": 26,
"metadata": {},
"outputs": [],
"source": [
@ -62,16 +82,16 @@
" unique_lengths_in_micro = unique_lengths * 1e6\n",
"\n",
" def update_plot(selected_length, x_value=None, y_value=None):\n",
" plt.figure(figsize=(8, 6)) # Ensure the plot is drawn fresh for each update\n",
" plt.figure(figsize=(12, 8)) # Make the figure wider (adjust as needed)\n",
"\n",
" if selected_length == \"Show All\":\n",
" mask = np.ones_like(length_flat, dtype=bool)\n",
" else:\n",
" selected_length_in_micro = float(selected_length.replace(' μm', ''))\n",
" tolerance = 0.1\n",
" # Recalculate the mask with matching shapes\n",
" tolerance = 0.01 # Tighten the tolerance to avoid unwanted data points\n",
" mask = np.abs(length_flat * 1e6 - selected_length_in_micro) < tolerance\n",
"\n",
" # Apply the mask to the data\n",
" x_values_for_length = x_values_flat[mask]\n",
" y_values_for_length = y_values_flat[mask] * y_multiplier\n",
" z_values_for_length = z_values_flat[mask]\n",
@ -126,30 +146,38 @@
" length_widget = widgets.Dropdown(\n",
" options=dropdown_options,\n",
" value=dropdown_options[0],\n",
" description='Select Length:',\n",
" description='Length:',\n",
" layout=widgets.Layout(width='500px') # Make the dropdown wider\n",
" )\n",
"\n",
" x_value_widget = widgets.FloatText(\n",
" value=np.mean(x_values_flat),\n",
" description=f\"Select {x_axis_name}:\",\n",
" disabled=False\n",
" description=f\"{x_axis_name}:\",\n",
" disabled=False,\n",
" layout=widgets.Layout(width='300px', margin='0 40px 0 0'), # Push input boxes more to the right\n",
" description_width='150px' # Smaller description width\n",
" )\n",
"\n",
" y_value_widget = widgets.FloatText(\n",
" value=None,\n",
" description=f\"Set {y_axis_name}:\",\n",
" disabled=True\n",
" description=f\"{y_axis_name}:\",\n",
" disabled=True,\n",
" layout=widgets.Layout(width='300px', margin='0 40px 0 0'), # Push input boxes more to the right\n",
" description_width='150px' # Smaller description width\n",
" )\n",
"\n",
" z_value_widget = widgets.FloatText(\n",
" value=None,\n",
" description=f\"Corresponding z value:\",\n",
" disabled=True\n",
" description=f\" Vgs:\",\n",
" disabled=True,\n",
" layout=widgets.Layout(width='300px', margin='0 40px 0 0'), # Push input boxes more to the right\n",
" description_width='150px' # Smaller description width\n",
" )\n",
"\n",
" select_x_or_y_widget = widgets.Checkbox(\n",
" value=True,\n",
" description=f\"Select {x_axis_name} (uncheck for {y_axis_name})\",\n",
" description=f\"{x_axis_name} (uncheck for {y_axis_name})\",\n",
" layout=widgets.Layout(width='300px') # Make the checkbox wider\n",
" )\n",
"\n",
" def toggle_x_or_y(change):\n",
@ -237,7 +265,7 @@
},
{
"cell_type": "code",
"execution_count": 126,
"execution_count": 27,
"metadata": {},
"outputs": [],
"source": [
@ -251,7 +279,7 @@
},
{
"cell_type": "code",
"execution_count": 127,
"execution_count": 28,
"metadata": {},
"outputs": [],
"source": [
@ -261,18 +289,18 @@
},
{
"cell_type": "code",
"execution_count": 128,
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "7d85aaf18b7e4afb84f5464597cbeb06",
"model_id": "c19c949de81145fbb8f56ff8c2747ab1",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
"VBox(children=(Dropdown(description='Length:', layout=Layout(width='500px'), options=('Show All', '0.20 μm', '…"
]
},
"metadata": {},
@ -292,7 +320,7 @@
},
{
"cell_type": "code",
"execution_count": 129,
"execution_count": 9,
"metadata": {},
"outputs": [
{
@ -301,7 +329,7 @@
"7.949913192125772e-05"
]
},
"execution_count": 129,
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
@ -318,7 +346,7 @@
},
{
"cell_type": "code",
"execution_count": 130,
"execution_count": 10,
"metadata": {},
"outputs": [
{
@ -341,18 +369,18 @@
},
{
"cell_type": "code",
"execution_count": 131,
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "833bb160c6a040f1aab4ff0562870238",
"model_id": "1fec87aafa2d4d009fc5065b0c919616",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.25 μm', '0.30 μm', '0…"
]
},
"metadata": {},
@ -372,7 +400,7 @@
},
{
"cell_type": "code",
"execution_count": 132,
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
@ -383,7 +411,7 @@
},
{
"cell_type": "code",
"execution_count": 133,
"execution_count": 13,
"metadata": {},
"outputs": [
{
@ -406,18 +434,18 @@
},
{
"cell_type": "code",
"execution_count": 134,
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "f844ac0b2e5c48ef9c44cf4f63247c08",
"model_id": "d9ae4f5537e44eb98cb8f43a99ace9e6",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.30 μm', '0.40 μm', '0…"
]
},
"metadata": {},
@ -426,12 +454,12 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "cf6424ea716549f4ad1dfc93487e5e34",
"model_id": "86826b2d41334c9f97fc4c71228b844b",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.25 μm', '0.30 μm', '0…"
]
},
"metadata": {},
@ -445,7 +473,7 @@
},
{
"cell_type": "code",
"execution_count": 135,
"execution_count": 15,
"metadata": {},
"outputs": [
{
@ -515,7 +543,7 @@
},
{
"cell_type": "code",
"execution_count": 136,
"execution_count": 16,
"metadata": {},
"outputs": [
{
@ -524,7 +552,7 @@
"2.0004294796936965e-06"
]
},
"execution_count": 136,
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
@ -542,7 +570,7 @@
},
{
"cell_type": "code",
"execution_count": 137,
"execution_count": 17,
"metadata": {},
"outputs": [
{
@ -562,7 +590,7 @@
},
{
"cell_type": "code",
"execution_count": 138,
"execution_count": 18,
"metadata": {},
"outputs": [
{
@ -590,18 +618,18 @@
},
{
"cell_type": "code",
"execution_count": 139,
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "fa44bbefc0154f809fc62bb1f4a41d7e",
"model_id": "653d5df7f39548198c9e9ad5b226e0ed",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.25 μm', '0.30 μm', '0…"
]
},
"metadata": {},
@ -610,12 +638,12 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "c792d4dd57d0406fa39ab7b9779bdb9c",
"model_id": "45eaee19c87c4d629180ab7b63eae94a",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.30 μm', '0.40 μm', '0…"
]
},
"metadata": {},
@ -642,7 +670,7 @@
},
{
"cell_type": "code",
"execution_count": 140,
"execution_count": 20,
"metadata": {},
"outputs": [
{
@ -669,18 +697,18 @@
},
{
"cell_type": "code",
"execution_count": 141,
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "8cf6156d8983439b9e36b85a04dd0a8b",
"model_id": "777232b20ad64f47aaa584c273a69fe7",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.25 μm', '0.30 μm', '0…"
]
},
"metadata": {},
@ -689,12 +717,12 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "a53493719c6f4501acf45f7f50192f97",
"model_id": "7e6503685d4f4eadb9a22f5e2fd505c1",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.13 μm', '0.26 μm', '0.39 μm', '0…"
"VBox(children=(Dropdown(description='Select Length:', options=('Show All', '0.20 μm', '0.30 μm', '0.40 μm', '0…"
]
},
"metadata": {},
@ -711,7 +739,7 @@
},
{
"cell_type": "code",
"execution_count": 142,
"execution_count": 22,
"metadata": {},
"outputs": [
{
@ -764,7 +792,7 @@
},
{
"cell_type": "code",
"execution_count": 143,
"execution_count": 23,
"metadata": {},
"outputs": [
{
@ -870,7 +898,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@ -884,7 +912,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.5"
"version": "3.10.12"
}
},
"nbformat": 4,

View File

@ -1,201 +0,0 @@
v {xschem version=3.4.5 file_version=1.2
}
G {}
K {}
V {}
S {}
E {}
N 370 -560 390 -560 {
lab=#net1}
N 370 -620 370 -560 {
lab=#net1}
N 370 -620 430 -620 {
lab=#net1}
N 130 -560 130 -460 {
lab=GND}
N 280 -460 430 -460 {
lab=GND}
N 130 -740 230 -740 {
lab=v-}
N 280 -965 280 -940 {
lab=vdd}
N 130 -940 280 -940 {
lab=vdd}
N 130 -840 130 -740 {
lab=v-}
N 430 -750 430 -740 {
lab=v+}
N 430 -740 430 -710 {
lab=v+}
N 430 -560 430 -460 {
lab=GND}
N 430 -620 430 -590 {
lab=#net1}
N 430 -650 430 -620 {
lab=#net1}
N 280 -460 280 -430 {
lab=GND}
N 130 -460 280 -460 {
lab=GND}
N 170 -870 390 -870 {
lab=Vo1}
N 595 -900 595 -870 {
lab=Vo1}
N 430 -750 595 -750 {
lab=v+}
N 980 -770 1030 -770 {
lab=VBG}
N 635 -840 635 -770 {
lab=VBG}
N 315 -740 430 -740 {
lab=v+}
N 430 -840 430 -750 {
lab=v+}
N 595 -750 595 -710 {
lab=v+}
N 430 -460 595 -460 {
lab=GND}
N 595 -650 595 -460 {
lab=GND}
N 430 -940 635 -940 {
lab=vdd}
N 980 -655 980 -460 {
lab=GND}
N 800 -460 980 -460 {
lab=GND}
N 980 -770 980 -715 {
lab=VBG}
N 800 -770 980 -770 {
lab=VBG}
N 635 -940 635 -900 {
lab=vdd}
N 635 -870 725 -870 {
lab=vdd}
N 430 -940 430 -900 {
lab=vdd}
N 280 -940 430 -940 {
lab=vdd}
N 430 -870 495 -870 {
lab=vdd}
N 130 -940 130 -900 {
lab=vdd}
N 70 -870 130 -870 {
lab=vdd}
N 130 -740 130 -590 {
lab=v-}
N 170 -560 315 -560 {
lab=v+}
N 315 -740 315 -560 {
lab=v+}
N 800 -770 800 -715 {
lab=VBG}
N 635 -770 800 -770 {
lab=VBG}
N 800 -655 800 -460 {
lab=GND}
N 595 -460 800 -460 {
lab=GND}
N 1195 -730 1195 -700 {
lab=bulk7}
N 1195 -640 1195 -600 {
lab=vdd}
N 1290 -640 1290 -605 {
lab=vss}
N 1290 -725 1290 -700 {
lab=sub!}
C {sg13g2_pr/sg13_lv_nmos.sym} 150 -560 2 0 {name=M1
l=5u
w=7.14u
ng=4
m=1
model=sg13_lv_nmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_nmos.sym} 410 -560 2 1 {name=M2
l=5u
w=21u
ng=8
m=1
model=sg13_lv_nmos
spiceprefix=X
}
C {gnd.sym} 280 -430 0 0 {name=l3 lab=GND}
C {sg13g2_pr/sg13_lv_pmos.sym} 150 -870 0 1 {name=M3
l=5u
w=15u
ng=8
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_pmos.sym} 410 -870 0 0 {name=M4
l=5u
w=15u
ng=8
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 280 -965 0 1 {name=p2 sig_type=std_logic lab=vdd}
C {lab_pin.sym} 230 -740 3 0 {name=p3 sig_type=std_logic lab=v-}
C {lab_pin.sym} 315 -740 0 0 {name=p8 sig_type=std_logic lab=v+}
C {lab_pin.sym} 285 -870 1 1 {name=p9 sig_type=std_logic lab=Vo1}
C {sg13g2_pr/sg13_lv_pmos.sym} 615 -870 0 0 {name=M5
l=5u
w=16u
ng=8
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {opin.sym} 1030 -770 0 0 {name=p11 lab=VBG}
C {lab_pin.sym} 595 -900 2 1 {name=p12 sig_type=std_logic lab=Vo1}
C {lab_pin.sym} 70 -870 0 0 {name=p13 sig_type=std_logic lab=vdd}
C {lab_pin.sym} 495 -870 0 1 {name=p14 sig_type=std_logic lab=vdd}
C {lab_pin.sym} 725 -870 0 1 {name=p15 sig_type=std_logic lab=vdd}
C {sg13g2_pr/cap_cmim.sym} 980 -685 0 0 {name=C3
model=cap_cmim
w=72.965e-6
l=72.965e-6
m=1
spiceprefix=X}
C {sg13g2_pr/rppd.sym} 430 -680 0 0 {name=R3
w=0.5e-6
l=194.345e-6
model=rppd
spiceprefix=X
b=0
m=1
}
C {sg13g2_pr/rppd.sym} 595 -680 0 0 {name=R1
w=0.6e-6
l=194.345e-6
model=rppd
spiceprefix=X
b=0
m=1
}
C {sg13g2_pr/rppd.sym} 800 -685 0 0 {name=R2
w=0.5e-6
l=192.395e-6
model=rppd
spiceprefix=X
b=0
m=1
}
C {lab_pin.sym} 1195 -730 0 1 {name=p67 sig_type=std_logic lab=bulk7}
C {lab_pin.sym} 1195 -600 0 0 {name=p68 sig_type=std_logic lab=vdd}
C {sg13g2_pr/ntap1.sym} 1195 -670 0 0 {name=R27
model=ntap1
spiceprefix=X
R=8.463
Imax=0.3e-6
}
C {sg13g2_pr/ptap1.sym} 1290 -670 0 0 {name=R29
model=ptap1
spiceprefix=X
w=10e-6
l=1.0e-6
}
C {lab_pin.sym} 1290 -605 0 0 {name=p81 sig_type=std_logic lab=vss}
C {lab_pin.sym} 1290 -725 0 1 {name=p82 sig_type=std_logic lab=sub!}

View File

@ -1,5 +1,4 @@
v {xschem version=3.4.5 file_version=1.2
}
v {xschem version=3.4.6 file_version=1.2}
G {}
K {}
V {}
@ -53,7 +52,7 @@ N 450 -305 450 -245 {
lab=#net3}
N 450 -305 510 -305 {
lab=#net3}
N 510 -145 675 -145 {
N 675 -145 880 -145 {
lab=vss}
N 210 -760 310 -760 {
lab=v-}
@ -283,32 +282,26 @@ N 510 -585 510 -565 {
lab=#net6}
N 510 -670 510 -645 {
lab=#net7}
N 675 -350 675 -145 {
lab=vss}
N 675 -425 675 -410 {
lab=#net8}
N 675 -500 675 -485 {
lab=#net9}
lab=#net8}
N 675 -585 675 -560 {
lab=#net10}
lab=#net9}
N 675 -670 675 -645 {
lab=#net11}
lab=#net10}
N 880 -240 880 -145 {
lab=vss}
N 675 -145 880 -145 {
lab=vss}
N 880 -675 880 -660 {
lab=#net12}
lab=#net11}
N 880 -600 880 -590 {
lab=#net13}
lab=#net12}
N 880 -530 880 -520 {
lab=#net14}
lab=#net13}
N 880 -460 880 -445 {
lab=#net15}
lab=#net14}
N 880 -385 880 -375 {
lab=#net16}
lab=#net15}
N 880 -315 880 -300 {
lab=#net17}
lab=#net16}
N 210 -215 210 -145 {
lab=vss}
N 250 -245 395 -245 {
@ -329,86 +322,6 @@ N 1755 -620 1755 -590 {
lab=bulk7}
N 1755 -530 1755 -490 {
lab=vdd}
N 1690 -855 1690 -785 {
lab=vdd}
N 1690 -855 1730 -855 {
lab=vdd}
N 1730 -855 1730 -815 {
lab=vdd}
N 1690 -785 1730 -785 {
lab=vdd}
N 1825 -855 1825 -785 {
lab=vdd}
N 1825 -855 1865 -855 {
lab=vdd}
N 1865 -855 1865 -815 {
lab=vdd}
N 1825 -785 1865 -785 {
lab=vdd}
N 1695 -770 1695 -700 {
lab=vdd}
N 1695 -770 1735 -770 {
lab=vdd}
N 1735 -770 1735 -730 {
lab=vdd}
N 1695 -700 1735 -700 {
lab=vdd}
N 1830 -770 1830 -700 {
lab=vdd}
N 1830 -770 1870 -770 {
lab=vdd}
N 1870 -770 1870 -730 {
lab=vdd}
N 1830 -700 1870 -700 {
lab=vdd}
N 1950 -855 1950 -785 {
lab=vdd}
N 1950 -855 1990 -855 {
lab=vdd}
N 1990 -855 1990 -815 {
lab=vdd}
N 1950 -785 1990 -785 {
lab=vdd}
N 1950 -775 1950 -705 {
lab=vdd}
N 1950 -775 1990 -775 {
lab=vdd}
N 1990 -775 1990 -735 {
lab=vdd}
N 1950 -705 1990 -705 {
lab=vdd}
N 2075 -775 2075 -705 {
lab=vdd}
N 2075 -775 2115 -775 {
lab=vdd}
N 2115 -775 2115 -735 {
lab=vdd}
N 2075 -705 2115 -705 {
lab=vdd}
N 2075 -855 2075 -785 {
lab=vdd}
N 2075 -855 2115 -855 {
lab=vdd}
N 2115 -855 2115 -815 {
lab=vdd}
N 2075 -785 2115 -785 {
lab=vdd}
N 2200 -775 2200 -705 {
lab=vdd}
N 2200 -775 2240 -775 {
lab=vdd}
N 2240 -775 2240 -735 {
lab=vdd}
N 2200 -705 2240 -705 {
lab=vdd}
N 2200 -855 2200 -785 {
lab=vdd}
N 2200 -855 2240 -855 {
lab=vdd}
N 2240 -855 2240 -815 {
lab=vdd}
N 2200 -785 2240 -785 {
lab=vdd}
N 1355 -365 1355 -330 {
lab=vss}
N 1355 -450 1355 -425 {
@ -429,13 +342,12 @@ N 1670 -360 1670 -325 {
lab=vss}
N 1670 -445 1670 -420 {
lab=sub!}
C {devices/code_shown.sym} 1110 -215 0 0 {name=MODEL only_toplevel=true
format="tcleval( @value )"
value="
.lib $::SG13G2_MODELS/cornerCAP.lib cap_typ
.lib $::SG13G2_MODELS/cornerRES.lib res_typ
.lib cornerMOSlv.lib mos_tt
"}
N 1887.5 -607.5 1887.5 -585 {lab=vdd}
N 1887.5 -527.5 1887.5 -507.5 {lab=vss}
N 675 -340 675 -145 {lab=vss}
N 510 -145 675 -145 {
lab=vss}
N 675 -425 675 -400 {lab=#net17}
C {sg13g2_pr/sg13_lv_nmos.sym} 1520 -1320 2 0 {name=M8
l=10u
w=150n
@ -471,7 +383,7 @@ spiceprefix=X
C {lab_pin.sym} 1735 -1535 0 1 {name=p5 sig_type=std_logic lab=vdd}
C {lab_pin.sym} 1690 -1485 0 1 {name=p4 sig_type=std_logic lab=bulk6}
C {lab_pin.sym} 1440 -1485 0 0 {name=p17 sig_type=std_logic lab=bulk6}
C {sg13g2_pr/sg13_lv_nmos.sym} 230 -245 2 0 {name=M1
C {sg13g2_pr/sg13_lv_nmos.sym} 230 -245 0 1 {name=M1
l=5u
w=7.14u
ng=4
@ -479,7 +391,7 @@ m=1
model=sg13_lv_nmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_nmos.sym} 490 -245 2 1 {name=M2
C {sg13g2_pr/sg13_lv_nmos.sym} 490 -245 0 0 {name=M2
l=5u
w=21u
ng=8
@ -591,7 +503,7 @@ spiceprefix=X
C {lab_pin.sym} 1255 -865 0 0 {name=p50 sig_type=std_logic lab=vdd}
C {lab_pin.sym} 1295 -785 0 0 {name=p53 sig_type=std_logic lab=dn2}
C {lab_pin.sym} 1490 -785 0 0 {name=p54 sig_type=std_logic lab=dn2}
C {sg13g2_pr/ntap1.sym} 1300 -560 0 0 {name=R4
C {sg13g2_pr/ntap1.sym} 1300 -560 2 0 {name=R4
model=ntap1
spiceprefix=X
w=13e-6
@ -615,7 +527,7 @@ spiceprefix=X
C {iopin.sym} 505 -1070 1 1 {name=p30 lab=vss}
C {iopin.sym} 505 -1580 1 1 {name=p31 lab=vdd}
C {lab_pin.sym} 695 -1130 0 1 {name=p32 sig_type=std_logic lab=sub!}
C {sg13g2_pr/ptap1.sym} 1275 -400 0 0 {name=R5
C {sg13g2_pr/ptap1.sym} 1275 -400 2 0 {name=R5
model=ptap1
spiceprefix=X
w=10e-6
@ -625,7 +537,7 @@ C {lab_pin.sym} 1275 -335 0 0 {name=p33 sig_type=std_logic lab=vss}
C {lab_pin.sym} 1275 -455 0 1 {name=p34 sig_type=std_logic lab=sub!}
C {lab_pin.sym} 1415 -495 0 0 {name=p35 sig_type=std_logic lab=vdd}
C {lab_pin.sym} 1415 -615 0 1 {name=p36 sig_type=std_logic lab=bulk1}
C {sg13g2_pr/ntap1.sym} 1415 -560 0 0 {name=R6
C {sg13g2_pr/ntap1.sym} 1415 -560 2 0 {name=R6
model=ntap1
spiceprefix=X
w=0.78e-6
@ -686,7 +598,7 @@ C {lab_pin.sym} 75 -1530 0 0 {name=p46 sig_type=std_logic lab=bulk4}
C {lab_pin.sym} 950 -1525 2 0 {name=p47 sig_type=std_logic lab=bulk4}
C {lab_pin.sym} 1520 -620 0 1 {name=p48 sig_type=std_logic lab=bulk4}
C {lab_pin.sym} 1520 -490 0 0 {name=p51 sig_type=std_logic lab=vdd}
C {sg13g2_pr/ntap1.sym} 1520 -560 0 0 {name=R8
C {sg13g2_pr/ntap1.sym} 1520 -560 2 0 {name=R8
model=ntap1
spiceprefix=X
R=262.847.0
@ -701,7 +613,7 @@ C {lab_pin.sym} 675 -1385 0 1 {name=p19 sig_type=std_logic lab=v+}
C {lab_pin.sym} 1810 -1205 3 0 {name=p1 sig_type=std_logic lab=v-}
C {lab_pin.sym} 1675 -620 0 1 {name=p7 sig_type=std_logic lab=bulk6}
C {lab_pin.sym} 1675 -490 0 0 {name=p16 sig_type=std_logic lab=vdd}
C {sg13g2_pr/ntap1.sym} 1675 -560 0 0 {name=R9
C {sg13g2_pr/ntap1.sym} 1675 -560 2 0 {name=R9
model=ntap1
spiceprefix=X
R=8.463
@ -749,14 +661,6 @@ spiceprefix=X
b=0
m=1
}
C {sg13g2_pr/rppd.sym} 675 -700 0 0 {name=R1
w=0.5e-6
l=38.65e-6
model=rppd
spiceprefix=X
b=0
m=1
}
C {sg13g2_pr/rppd.sym} 675 -615 0 0 {name=R16
w=0.5e-6
l=38.65e-6
@ -781,7 +685,7 @@ spiceprefix=X
b=0
m=1
}
C {sg13g2_pr/rppd.sym} 675 -380 0 0 {name=R19
C {sg13g2_pr/rppd.sym} 675 -370 0 0 {name=R19
w=3e-6
l=38.65e-6
model=rppd
@ -842,7 +746,7 @@ C {lab_pin.sym} 1435 -1320 0 0 {name=p63 sig_type=std_logic lab=sub!}
C {lab_pin.sym} 1800 -1405 0 1 {name=p64 sig_type=std_logic lab=bulk5}
C {lab_pin.sym} 1600 -620 0 1 {name=p65 sig_type=std_logic lab=bulk5}
C {lab_pin.sym} 1600 -490 0 0 {name=p66 sig_type=std_logic lab=vdd}
C {sg13g2_pr/ntap1.sym} 1600 -560 0 0 {name=R26
C {sg13g2_pr/ntap1.sym} 1600 -560 2 0 {name=R26
model=ntap1
spiceprefix=X
R=8.463
@ -850,103 +754,13 @@ Imax=0.3e-6
}
C {lab_pin.sym} 1755 -620 0 1 {name=p67 sig_type=std_logic lab=bulk7}
C {lab_pin.sym} 1755 -490 0 0 {name=p68 sig_type=std_logic lab=vdd}
C {sg13g2_pr/ntap1.sym} 1755 -560 0 0 {name=R27
C {sg13g2_pr/ntap1.sym} 1755 -560 2 0 {name=R27
model=ntap1
spiceprefix=X
R=8.463
Imax=0.3e-6
}
C {sg13g2_pr/sg13_lv_pmos.sym} 1710 -815 0 0 {name=M22
l=5u
w=10u
ng=4
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 1690 -855 0 0 {name=p69 sig_type=std_logic lab=vdd}
C {sg13g2_pr/sg13_lv_pmos.sym} 1845 -815 0 0 {name=M23
l=5u
w=10u
ng=4
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 1825 -855 0 0 {name=p70 sig_type=std_logic lab=vdd}
C {sg13g2_pr/sg13_lv_pmos.sym} 1715 -730 0 0 {name=M24
l=5u
w=10u
ng=4
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 1695 -770 0 0 {name=p71 sig_type=std_logic lab=vdd}
C {sg13g2_pr/sg13_lv_pmos.sym} 1850 -730 0 0 {name=M25
l=5u
w=10u
ng=4
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 1830 -770 0 0 {name=p72 sig_type=std_logic lab=vdd}
C {sg13g2_pr/sg13_lv_pmos.sym} 1970 -815 0 0 {name=M26
l=5u
w=10u
ng=4
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 1950 -855 0 0 {name=p73 sig_type=std_logic lab=vdd}
C {sg13g2_pr/sg13_lv_pmos.sym} 1970 -735 0 0 {name=M27
l=4u
w=5u
ng=2
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 1950 -775 0 0 {name=p74 sig_type=std_logic lab=vdd}
C {sg13g2_pr/sg13_lv_pmos.sym} 2095 -735 0 0 {name=M28
l=4u
w=5u
ng=2
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 2075 -775 0 0 {name=p75 sig_type=std_logic lab=vdd}
C {sg13g2_pr/sg13_lv_pmos.sym} 2095 -815 0 0 {name=M29
l=4u
w=5u
ng=2
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 2075 -855 0 0 {name=p76 sig_type=std_logic lab=vdd}
C {sg13g2_pr/sg13_lv_pmos.sym} 2220 -735 0 0 {name=M30
l=4u
w=5u
ng=2
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 2200 -775 0 0 {name=p77 sig_type=std_logic lab=vdd}
C {sg13g2_pr/sg13_lv_pmos.sym} 2220 -815 0 0 {name=M31
l=4u
w=5u
ng=2
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 2200 -855 0 0 {name=p78 sig_type=std_logic lab=vdd}
C {sg13g2_pr/ptap1.sym} 1355 -395 0 0 {name=R7
C {sg13g2_pr/ptap1.sym} 1355 -395 2 0 {name=R7
model=ptap1
spiceprefix=X
w=10e-6
@ -954,7 +768,7 @@ l=1.0e-6
}
C {lab_pin.sym} 1355 -330 0 0 {name=p42 sig_type=std_logic lab=vss}
C {lab_pin.sym} 1355 -450 0 1 {name=p44 sig_type=std_logic lab=sub!}
C {sg13g2_pr/ptap1.sym} 1430 -395 0 0 {name=R10
C {sg13g2_pr/ptap1.sym} 1430 -395 2 0 {name=R10
model=ptap1
spiceprefix=X
w=10e-6
@ -962,7 +776,7 @@ l=1.0e-6
}
C {lab_pin.sym} 1430 -330 0 0 {name=p58 sig_type=std_logic lab=vss}
C {lab_pin.sym} 1430 -450 0 1 {name=p59 sig_type=std_logic lab=sub!}
C {sg13g2_pr/ptap1.sym} 1510 -395 0 0 {name=R11
C {sg13g2_pr/ptap1.sym} 1510 -395 2 0 {name=R11
model=ptap1
spiceprefix=X
w=10e-6
@ -970,7 +784,7 @@ l=1.0e-6
}
C {lab_pin.sym} 1510 -330 0 0 {name=p60 sig_type=std_logic lab=vss}
C {lab_pin.sym} 1510 -450 0 1 {name=p61 sig_type=std_logic lab=sub!}
C {sg13g2_pr/ptap1.sym} 1590 -395 0 0 {name=R28
C {sg13g2_pr/ptap1.sym} 1590 -395 2 0 {name=R28
model=ptap1
spiceprefix=X
w=10e-6
@ -978,7 +792,7 @@ l=1.0e-6
}
C {lab_pin.sym} 1590 -330 0 0 {name=p79 sig_type=std_logic lab=vss}
C {lab_pin.sym} 1590 -450 0 1 {name=p80 sig_type=std_logic lab=sub!}
C {sg13g2_pr/ptap1.sym} 1670 -390 0 0 {name=R29
C {sg13g2_pr/ptap1.sym} 1670 -390 2 0 {name=R29
model=ptap1
spiceprefix=X
w=10e-6
@ -986,3 +800,19 @@ l=1.0e-6
}
C {lab_pin.sym} 1670 -325 0 0 {name=p81 sig_type=std_logic lab=vss}
C {lab_pin.sym} 1670 -445 0 1 {name=p82 sig_type=std_logic lab=sub!}
C {sg13g2_pr/cap_cmim.sym} 1887.5 -555 0 0 {name=C3
model=cap_cmim
w=5e-6
l=5e-6
m=15
spiceprefix=X}
C {lab_pin.sym} 1887.5 -607.5 0 1 {name=p83 sig_type=std_logic lab=vdd}
C {lab_pin.sym} 1887.5 -507.5 0 1 {name=p84 sig_type=std_logic lab=vss}
C {sg13g2_pr/rppd.sym} 675 -700 0 0 {name=R31
w=0.5e-6
l=38.65e-6
model=rppd
spiceprefix=X
b=0
m=1
}

View File

@ -17,7 +17,7 @@ M5 vout iout vdd bulk4 sg13_lv_pmos l=2.08u w=75u ng=8 m=1
M6 vout dn4 vss bulk3 sg13_lv_nmos l=9.75u w=28.8u ng=4 m=1
M9 iout iout vdd bulk4 sg13_lv_pmos l=2.08u w=75u ng=8 m=1
C2 dn4 vout cap_cmim w=22.295e-6 l=22.295e-6 m=1
R4 vss bulk2 ptap1 A = 68.4036e-12, P = 0.24264e-3
R5 vdd bulk3 ntap1 A = 95.5222e-12, P = 0.21292e-3
R4 vss bulk2 ptap1 A = 9.64655e-12, P = 0.18e-3
R5 vdd bulk3 ntap1 A = 27.435e-12, P = 0.177e-3
.ends
.end

View File

@ -1,5 +1,4 @@
v {xschem version=3.4.5 file_version=1.2
}
v {xschem version=3.4.6 file_version=1.2}
G {}
K {}
V {}
@ -197,7 +196,7 @@ N 660 -305 660 -255 {
lab=vss}
N 450 -305 450 -260 {
lab=vss}
C {sg13g2_pr/sg13_lv_nmos.sym} 640 -365 2 1 {name=M4
C {sg13g2_pr/sg13_lv_nmos.sym} 640 -365 0 0 {name=M4
l=9.75u
w=720n
ng=1
@ -205,7 +204,7 @@ m=1
model=sg13_lv_nmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_nmos.sym} 470 -365 2 0 {name=M3
C {sg13g2_pr/sg13_lv_nmos.sym} 470 -365 0 1 {name=M3
l=9.75u
w=720n
ng=1
@ -245,7 +244,7 @@ m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_nmos.sym} 840 -400 2 1 {name=M6
C {sg13g2_pr/sg13_lv_nmos.sym} 840 -400 0 0 {name=M6
l=9.75u
w=28.8u
ng=4

View File

@ -1,4 +1,4 @@
* Extracted by KLayout with SG13G2 LVS runset on : 03/03/2025 11:24
* Extracted by KLayout with SG13G2 LVS runset on : 28/03/2025 08:06
.SUBCKT two_stage_OTA_layout vss vdd dn3 iout vout dn2 v+ v\x2d vss$1 dn4
M$1 vss dn3 dn3 vss sg13_lv_nmos L=9.75u W=0.72u AS=0.2448p AD=0.2448p PS=2.12u

View File

@ -1,14 +1,15 @@
* Extracted by KLayout with SG13G2 LVS runset on : 18/03/2025 08:57
* Extracted by KLayout with SG13G2 LVS runset on : 27/03/2025 15:48
.SUBCKT input_common_centroid v+ v\x2d vdd dn4 dn3
M$1 vdd vdd \$6 \$27 sg13_lv_pmos L=3.7u W=14.56u AS=4.9504p AD=4.9504p
.SUBCKT input_common_centroid vdd dn3 v+ dn4 v\x2d
M$1 vdd vdd \$9 \$2 sg13_lv_pmos L=3.7u W=14.56u AS=4.9504p AD=4.9504p
+ PS=31.84u PD=31.84u
M$2 \$6 v+ dn4 \$27 sg13_lv_pmos L=3.7u W=7.28u AS=2.4752p AD=2.4752p PS=15.92u
M$3 \$9 v+ dn4 \$2 sg13_lv_pmos L=3.7u W=7.28u AS=2.4752p AD=2.4752p PS=15.92u
+ PD=15.92u
M$3 dn4 vdd vdd \$27 sg13_lv_pmos L=3.7u W=7.28u AS=2.4752p AD=2.4752p
+ PS=15.92u PD=15.92u
M$5 \$6 v\x2d dn3 \$27 sg13_lv_pmos L=3.7u W=7.28u AS=2.4752p AD=2.4752p
+ PS=15.92u PD=15.92u
M$6 dn3 vdd vdd \$27 sg13_lv_pmos L=3.7u W=7.28u AS=2.4752p AD=2.4752p
M$4 \$9 v\x2d dn3 \$2 sg13_lv_pmos L=3.7u W=7.28u AS=2.4752p AD=2.4752p
+ PS=15.92u PD=15.92u
M$5 dn4 vdd vdd \$2 sg13_lv_pmos L=3.7u W=7.28u AS=2.4752p AD=2.4752p PS=15.92u
+ PD=15.92u
M$6 dn3 vdd vdd \$2 sg13_lv_pmos L=3.7u W=7.28u AS=2.4752p AD=2.4752p PS=15.92u
+ PD=15.92u
R$13 \$2 vdd ntap1 A=28.7556p P=185.52u
.ENDS input_common_centroid

View File

@ -1,5 +1,4 @@
v {xschem version=3.4.5 file_version=1.2
}
v {xschem version=3.4.6 file_version=1.2}
G {}
K {}
V {}
@ -121,7 +120,7 @@ spiceprefix=X
C {lab_pin.sym} 55 -445 0 0 {name=p50 sig_type=std_logic lab=vdd}
C {lab_pin.sym} 95 -365 0 0 {name=p53 sig_type=std_logic lab=dn2}
C {lab_pin.sym} 290 -365 0 0 {name=p54 sig_type=std_logic lab=dn2}
C {sg13g2_pr/ntap1.sym} 650 -240 0 0 {name=R1
C {sg13g2_pr/ntap1.sym} 650 -240 2 0 {name=R1
model=ntap1
spiceprefix=X
w=13e-6

View File

@ -1,283 +0,0 @@
v {xschem version=3.4.5 file_version=1.2
}
G {}
K {}
V {}
S {}
E {}
N 450 -495 450 -395 {
lab=dn3}
N 450 -525 660 -525 {
lab=bulk}
N 450 -575 450 -555 {
lab=dn2}
N 660 -575 660 -555 {
lab=dn2}
N 560 -575 660 -575 {
lab=dn2}
N 380 -525 410 -525 {
lab=v-}
N 700 -525 730 -525 {
lab=v+}
N 560 -595 560 -575 {
lab=dn2}
N 450 -575 560 -575 {
lab=dn2}
N 660 -495 660 -400 {
lab=dn4}
N 660 -335 715 -335 {
lab=bulk}
N 620 -365 620 -335 {
lab=#net1}
N 660 -305 660 -285 {
lab=dn4}
N 425 -345 480 -345 {
lab=bulk}
N 385 -375 385 -345 {
lab=#net2}
N 425 -315 425 -295 {
lab=dn3}
N 620 -365 660 -365 {
lab=#net1}
N 385 -375 425 -375 {
lab=#net2}
N 420 -645 475 -645 {
lab=bulk}
N 380 -675 380 -645 {
lab=#net3}
N 420 -615 420 -595 {
lab=dn2}
N 655 -655 710 -655 {
lab=bulk}
N 615 -685 615 -655 {
lab=#net4}
N 655 -625 655 -605 {
lab=dn2}
N 380 -675 420 -675 {
lab=#net3}
N 615 -685 655 -685 {
lab=#net4}
N 120 -375 120 -340 {
lab=bulk}
N 120 -285 120 -245 {
lab=vdd}
N 1170 -365 1225 -365 {
lab=bulk}
N 1130 -395 1130 -365 {
lab=#net5}
N 1170 -335 1170 -315 {
lab=dn4}
N 885 -355 940 -355 {
lab=bulk}
N 845 -385 845 -355 {
lab=#net6}
N 885 -325 885 -305 {
lab=dn3}
N 1130 -395 1170 -395 {
lab=#net5}
N 845 -385 885 -385 {
lab=#net6}
N 1090 -655 1145 -655 {
lab=bulk}
N 1050 -685 1050 -655 {
lab=#net7}
N 1090 -625 1090 -605 {
lab=dn2}
N 925 -645 980 -645 {
lab=bulk}
N 885 -675 885 -645 {
lab=#net8}
N 925 -615 925 -595 {
lab=dn2}
N 1050 -685 1090 -685 {
lab=#net7}
N 885 -675 925 -675 {
lab=#net8}
N 890 -495 890 -395 {
lab=dn3}
N 890 -525 1100 -525 {
lab=bulk}
N 890 -575 890 -555 {
lab=dn2}
N 1100 -575 1100 -555 {
lab=dn2}
N 1000 -575 1100 -575 {
lab=dn2}
N 820 -525 850 -525 {
lab=v-}
N 1140 -525 1170 -525 {
lab=v+}
N 1000 -595 1000 -575 {
lab=dn2}
N 890 -575 1000 -575 {
lab=dn2}
N 1100 -495 1100 -400 {
lab=dn4}
C {sg13g2_pr/sg13_lv_pmos.sym} 430 -525 0 0 {name=M1
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_pmos.sym} 680 -525 0 1 {name=M2
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {iopin.sym} 380 -525 0 1 {name=p10 lab=v-
m=1}
C {iopin.sym} 730 -525 0 0 {name=p11 lab=v+
m=1}
C {iopin.sym} 120 -245 2 1 {name=p1 lab=vdd}
C {lab_pin.sym} 450 -455 0 0 {name=p9 sig_type=std_logic lab=dn3
m=1}
C {lab_pin.sym} 660 -455 0 0 {name=p12 sig_type=std_logic lab=dn4
m=1}
C {sg13g2_pr/sg13_lv_pmos.sym} 640 -335 0 0 {name=M8
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_pmos.sym} 405 -345 0 0 {name=M10
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 120 -375 0 0 {name=p30 sig_type=std_logic lab=bulk}
C {lab_pin.sym} 560 -525 3 0 {name=p2 sig_type=std_logic lab=bulk
m=1}
C {lab_pin.sym} 480 -345 0 1 {name=p32 sig_type=std_logic lab=bulk
m=1}
C {lab_pin.sym} 715 -335 0 1 {name=p33 sig_type=std_logic lab=bulk
m=1}
C {lab_pin.sym} 560 -585 0 1 {name=p40 sig_type=std_logic lab=dn2
m=1}
C {lab_pin.sym} 425 -295 0 0 {name=p43 sig_type=std_logic lab=dn3
m=1}
C {lab_pin.sym} 660 -285 0 0 {name=p18 sig_type=std_logic lab=dn4
m=1}
C {sg13g2_pr/sg13_lv_pmos.sym} 400 -645 0 0 {name=M15
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_pmos.sym} 635 -655 0 0 {name=M16
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 710 -655 0 1 {name=p51 sig_type=std_logic lab=bulk
m=1}
C {lab_pin.sym} 475 -645 0 1 {name=p52 sig_type=std_logic lab=bulk
m=1}
C {lab_pin.sym} 655 -605 0 0 {name=p53 sig_type=std_logic lab=dn2
m=1}
C {lab_pin.sym} 420 -595 0 0 {name=p54 sig_type=std_logic lab=dn2
m=1}
C {sg13g2_pr/ntap1.sym} 120 -310 2 0 {name=R1
model=ntap1
spiceprefix=X
w=13e-6
l=34e-6
}
C {sg13g2_pr/sg13_lv_pmos.sym} 1150 -365 0 0 {name=M3
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_pmos.sym} 865 -355 0 0 {name=M4
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 940 -355 0 1 {name=p5 sig_type=std_logic lab=bulk
m=1}
C {lab_pin.sym} 1225 -365 0 1 {name=p6 sig_type=std_logic lab=bulk
m=1}
C {lab_pin.sym} 885 -305 0 0 {name=p7 sig_type=std_logic lab=dn3
m=1}
C {lab_pin.sym} 1170 -315 0 0 {name=p8 sig_type=std_logic lab=dn4
m=1}
C {sg13g2_pr/sg13_lv_pmos.sym} 1070 -655 0 0 {name=M5
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_pmos.sym} 905 -645 0 0 {name=M6
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {lab_pin.sym} 980 -645 0 1 {name=p16 sig_type=std_logic lab=bulk
m=1}
C {lab_pin.sym} 1145 -655 0 1 {name=p19 sig_type=std_logic lab=bulk
m=1}
C {lab_pin.sym} 925 -595 0 0 {name=p20 sig_type=std_logic lab=dn2
m=1}
C {lab_pin.sym} 1090 -605 0 0 {name=p21 sig_type=std_logic lab=dn2
m=1}
C {sg13g2_pr/sg13_lv_pmos.sym} 870 -525 0 0 {name=M7
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {sg13g2_pr/sg13_lv_pmos.sym} 1120 -525 0 1 {name=M9
l=3.7u
w=3.64u
ng=1
m=1
model=sg13_lv_pmos
spiceprefix=X
}
C {iopin.sym} 820 -525 0 1 {name=p22 lab=v-
m=1}
C {iopin.sym} 1170 -525 0 0 {name=p23 lab=v+
m=1}
C {lab_pin.sym} 890 -455 0 0 {name=p24 sig_type=std_logic lab=dn3
m=1}
C {lab_pin.sym} 1100 -455 0 0 {name=p25 sig_type=std_logic lab=dn4
m=1}
C {lab_pin.sym} 1000 -525 3 0 {name=p26 sig_type=std_logic lab=bulk
m=1}
C {lab_pin.sym} 1000 -585 0 1 {name=p27 sig_type=std_logic lab=dn2
m=1}
C {iopin.sym} 385 -375 2 0 {name=p3 lab=vdd}
C {iopin.sym} 620 -360 2 0 {name=p4 lab=vdd}
C {iopin.sym} 845 -380 2 0 {name=p13 lab=vdd}
C {iopin.sym} 1130 -390 2 0 {name=p14 lab=vdd}
C {iopin.sym} 1050 -675 2 0 {name=p15 lab=vdd}
C {iopin.sym} 885 -665 2 0 {name=p17 lab=vdd}
C {iopin.sym} 615 -680 2 0 {name=p28 lab=vdd}
C {iopin.sym} 380 -675 2 0 {name=p29 lab=vdd}

View File

@ -1,10 +1,12 @@
* Extracted by KLayout with SG13G2 LVS runset on : 13/02/2025 13:49
* Extracted by KLayout with SG13G2 LVS runset on : 27/03/2025 16:03
.SUBCKT input_stage dn3|dn4|vss vdd dn2 iout
M$1 dn3|dn4|vss dn3|dn4|vss dn3|dn4|vss \$1 sg13_lv_nmos L=9.75u W=1.44u
+ AS=0.4896p AD=0.4896p PS=4.24u PD=4.24u
M$3 dn2 iout vdd \$6 sg13_lv_pmos L=1.95u W=5.3u AS=1.802p AD=1.802p PS=11.28u
.SUBCKT input_stage vss dn4 dn3 vdd dn2 iout
M$1 vss dn3 dn4 \$1 sg13_lv_nmos L=9.75u W=0.72u AS=0.2448p AD=0.2448p PS=2.12u
+ PD=2.12u
M$2 vss dn3 dn3 \$1 sg13_lv_nmos L=9.75u W=0.72u AS=0.2448p AD=0.2448p PS=2.12u
+ PD=2.12u
M$3 dn2 iout vdd \$8 sg13_lv_pmos L=1.95u W=5.3u AS=1.802p AD=1.802p PS=11.28u
+ PD=11.28u
R$4 \$6 vdd ntap1 A=21.5031p P=43.5u
R$5 \$1 dn3|dn4|vss ptap1 A=15.54345p P=53.44u
R$4 \$8 vdd ntap1 A=7.5826p P=48.92u
R$5 \$1 vss ptap1 A=7.0975p P=56.78u
.ENDS input_stage

View File

@ -1,5 +1,4 @@
v {xschem version=3.4.5 file_version=1.2
}
v {xschem version=3.4.6 file_version=1.2}
G {}
K {}
V {}
@ -29,7 +28,7 @@ lab=vss}
N 265 -295 265 -260 {
lab=vss}
N 265 -380 265 -355 {
lab=bulk2}
lab=sub!}
N 865 -245 955 -245 {
lab=sub!}
N 655 -215 655 -185 {
@ -38,9 +37,9 @@ N 560 -245 655 -245 {
lab=sub!}
N 865 -215 865 -185 {
lab=vss}
N 385 -290 385 -255 {
N 425 -290 425 -255 {
lab=vdd}
N 385 -375 385 -350 {
N 425 -375 425 -350 {
lab=bulk1}
N 765 -580 765 -560 {
lab=vdd}
@ -57,7 +56,7 @@ spiceprefix=X
C {iopin.sym} 765 -185 1 1 {name=p16 lab=vss}
C {iopin.sym} 765 -580 1 1 {name=p19 lab=vdd}
C {lab_pin.sym} 955 -245 0 1 {name=p1 sig_type=std_logic lab=sub!}
C {sg13g2_pr/ptap1.sym} 265 -325 0 0 {name=R2
C {sg13g2_pr/ptap1.sym} 265 -325 2 0 {name=R2
model=ptap1
spiceprefix=X
w=10e-6
@ -69,9 +68,9 @@ C {iopin.sym} 865 -375 2 1 {name=p2 lab=dn4}
C {iopin.sym} 655 -375 2 1 {name=p5 lab=dn3}
C {iopin.sym} 765 -455 2 1 {name=p6 lab=dn2}
C {iopin.sym} 595 -530 2 0 {name=p7 lab=iout}
C {lab_pin.sym} 385 -255 0 0 {name=p8 sig_type=std_logic lab=vdd}
C {lab_pin.sym} 385 -375 0 1 {name=p10 sig_type=std_logic lab=bulk1}
C {sg13g2_pr/ntap1.sym} 385 -320 0 0 {name=R3
C {lab_pin.sym} 425 -255 0 0 {name=p8 sig_type=std_logic lab=vdd}
C {lab_pin.sym} 425 -375 0 1 {name=p10 sig_type=std_logic lab=bulk1}
C {sg13g2_pr/ntap1.sym} 425 -320 2 0 {name=R3
model=ntap1
spiceprefix=X
w=0.78e-6

View File

@ -1,12 +0,0 @@
** sch_path: /home/pedersen/projects/IHP-AnalogAcademy/modules/module_1_bandgap_reference/part_3_layout/OTA_layout/input_stage/schematic_mod/input_stage.sch
.subckt input_stage vss vdd dn4 dn3 dn2 iout
*.PININFO vss:B vdd:B dn4:B dn3:B dn2:B iout:B
M7 dn2 iout vdd bulk1 sg13_lv_pmos l=1.95u w=5.3u ng=1 m=1
R2 vss bulk2 ptap1 A = 7.0975e-12 P=56.78e-6
R3 vdd bulk1 ntap1 A = 7.5826e-12 P = 48.92e-6
M1 dn3 dn3 vss bulk2 sg13_lv_nmos l=9.75u w=720n ng=1 m=1
M2 dn4 dn3 vss bulk2 sg13_lv_nmos l=9.75u w=720n ng=1 m=1
.ends
.end

View File

@ -1,13 +1,13 @@
* Extracted by KLayout with SG13G2 LVS runset on : 14/01/2025 15:57
* Extracted by KLayout with SG13G2 LVS runset on : 27/03/2025 16:05
.SUBCKT output_stage vout dn4 vdd iout vout|vss vout$1 dn4$1
M$1 vout|vss dn4$1 vout|vss \$1 sg13_lv_nmos L=9.75u W=28.8u AS=6.552p
+ AD=6.552p PS=37.82u PD=37.82u
.SUBCKT output_stage vout dn4 vdd iout vss vout$1 vout$2 dn4$1
M$1 vss dn4$1 vout$2 \$1 sg13_lv_nmos L=9.75u W=28.8u AS=6.552p AD=6.552p
+ PS=37.82u PD=37.82u
M$5 vdd iout iout \$4 sg13_lv_pmos L=2.08u W=75u AS=15.65625p AD=15.65625p
+ PS=87.715u PD=87.715u
M$13 vdd iout vout$1 \$4 sg13_lv_pmos L=2.08u W=75u AS=15.65625p AD=15.65625p
+ PS=87.715u PD=87.715u
C$21 dn4 vout cap_cmim w=22.295u l=22.295u A=497.067025p P=89.18u m=1
R$22 \$4 vdd ntap1 A=95.5222p P=212.92u
R$23 \$1 vout|vss ptap1 A=68.4036p P=242.64u
R$22 \$4 vdd ntap1 A=27.435p P=177u
R$23 \$1 vss ptap1 A=25.19p P=201.52u
.ENDS output_stage

View File

@ -1,5 +1,4 @@
v {xschem version=3.4.5 file_version=1.2
}
v {xschem version=3.4.6 file_version=1.2}
G {}
K {}
V {}
@ -40,7 +39,7 @@ lab=vout}
N 860 -375 860 -305 {
lab=vss}
N 1265 -385 1265 -355 {
lab=bulk3}
lab=sub!}
N 1265 -295 1265 -255 {
lab=vss}
N 860 -400 940 -400 {
@ -98,7 +97,7 @@ C {lab_pin.sym} 1265 -385 0 0 {name=p23 sig_type=std_logic lab=sub!
}
C {lab_pin.sym} 1265 -255 2 0 {name=p24 sig_type=std_logic lab=vss}
C {lab_pin.sym} 940 -400 0 1 {name=p27 sig_type=std_logic lab=sub!}
C {sg13g2_pr/ptap1.sym} 1265 -325 0 0 {name=R2
C {sg13g2_pr/ptap1.sym} 1265 -325 2 0 {name=R2
model=ptap1
spiceprefix=X
R=262.847.0
@ -108,7 +107,7 @@ C {lab_pin.sym} 505 -625 0 0 {name=p36 sig_type=std_logic lab=bulk4}
C {lab_pin.sym} 930 -625 2 0 {name=p37 sig_type=std_logic lab=bulk4}
C {lab_pin.sym} 1455 -385 0 0 {name=p38 sig_type=std_logic lab=bulk4}
C {lab_pin.sym} 1455 -255 0 0 {name=p39 sig_type=std_logic lab=vdd}
C {sg13g2_pr/ntap1.sym} 1455 -325 0 0 {name=R5
C {sg13g2_pr/ntap1.sym} 1455 -325 2 0 {name=R5
model=ntap1
spiceprefix=X
R=262.847.0

View File

@ -1,379 +0,0 @@
"""Module to automatically generate a gallery of devices out of spice netlist creating a new GDS file. Can be used in
Klayout's batch mode. For example:
klayout -n sg13g2 -zz -r generator.py -rd netlist=netlist.spice -rd output=macros/gallery.gds
"""
import pathlib
import sys
import re
import argparse
from typing import List, Dict
import pya
import klayout.db
LIB = 'SG13_dev'
def parse_netlist(netlist: str) -> List[Dict[str, str]]:
# Define regular expression patterns for each component type
patterns = {
'ipin': re.compile(r"^\*\.ipin\s+(\S+)\s*$", re.MULTILINE),
'opin': re.compile(r"^\*\.opin\s+(\S+)\s*$", re.MULTILINE),
'iopin': re.compile(r"^\*\.iopin\s+(\S+)\s*$", re.MULTILINE),
'bjt': re.compile(r"^XQ(\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)\s+Nx=([\d\.]+)(?:\s+le=([\d\.e-]+))?\s*$", re.MULTILINE),
'capacitor': re.compile(r"^XC(\S+)\s+\S+\s+\S+\s+(\S+)\s+w=([\d\.e-]+)\s+l=([\d\.e-]+)\s+m=([\d\.]+)\s*$", re.MULTILINE),
'diode': re.compile(r"^XD(\S+)\s+\S+\s+\S+\s+(\S+)\s+l=([\d\.a-zA-Z]+)\s+w=([\d\.a-zA-Z]+)\s*$", re.MULTILINE),
'resistor': re.compile(r"^XR(\S+)\s+\S+\s+\S+\s+(\S+)\s+w=([\d\.e-]+)\s+l=([\d\.e-]+)\s+m=([\d\.]+)\s+b=([\d\.]+)\s*$", re.MULTILINE),
'mosfet': re.compile(r"^XM(\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)\s+w=([\d\.u-]+)\s+l=([\d\.u-]+)\s+ng=([\d\.]+)\s+m=([\d\.]+)(?:\s+rfmode=([\d\.]+))?\s*$", re.MULTILINE),
'tap': re.compile(r"^XR(\S+)\s+\S+\s+\S+\s+(\S+)\s*$", re.MULTILINE)
}
components = []
# Parse pins
for pin_type in ['ipin', 'opin', 'iopin']:
for match in patterns[pin_type].finditer(netlist):
components.append({
'name': match.group(1),
'model': pin_type
})
# Parse BJTs
for match in patterns['bjt'].finditer(netlist):
components.append({
'name': f"XQ{match.group(1)}",
'model': match.group(2),
'Nx': match.group(3),
'le': match.group(4) if match.group(4) else None
})
# Parse capacitors
for match in patterns['capacitor'].finditer(netlist):
components.append({
'name': f"XC{match.group(1)}",
'model': match.group(2),
'w': match.group(3),
'l': match.group(4),
'm': match.group(5)
})
# Parse diodes
for match in patterns['diode'].finditer(netlist):
components.append({
'name': f"XD{match.group(1)}",
'model': match.group(2),
'l': match.group(3),
'w': match.group(4)
})
# Parse resistors
for match in patterns['resistor'].finditer(netlist):
components.append({
'name': f"XR{match.group(1)}",
'model': match.group(2),
'w': match.group(3),
'l': match.group(4),
'm': match.group(5),
'b': match.group(6)
})
# Parse MOSFETs
for match in patterns['mosfet'].finditer(netlist):
components.append({
'name': f"XM{match.group(1)}",
'model': match.group(2),
'w': match.group(3),
'l': match.group(4),
'ng': match.group(5),
'm': match.group(6),
'rfmode': match.group(7) if match.group(7) else None
})
#Parse ntap
for match in patterns['tap'].finditer(netlist):
components.append({
'name': f"XR{match.group(1)}",
'model': match.group(2) # In this case, group 4 is the ntap model
})
return components
def generate_devices(netlist: str, output: str):
components = parse_netlist(netlist)
lib = pya.Library.library_by_name(LIB)
layout = klayout.db.Layout(True)
layout.dbu = 0.001
top_cell = layout.cell(layout.add_cell('gallery'))
res = find_instances_by_model(components, 'rppd')
xstep = 0
cell_w = 0
y_offset = 100
hmax = 0
for item in res:
width = float(item['w'])*1e6
length = float(item['l'])*1e6
bends = int(item['b'])
m = int(item['m'])
for i in range(1,m+1):
pcell_decl = lib.layout().pcell_declaration('rppd')
params = pcell_decl.params_as_hash(pcell_decl.get_parameters())
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), {'w': f'{width}u', 'l': f'{length}u', 'b' : f'{bends}'})
cell=layout.cell(pcell)
bbox=cell.bbox()
xstep = xstep + 100 + cell_w
top_cell.insert(klayout.db.CellInstArray(pcell, klayout.db.Trans(klayout.db.Point(xstep,y_offset ))))
cell_w = bbox.width()
cell_h = bbox.height()
if cell_h > hmax:
hmax = cell_h
# print(f'w: {cell_w}, h: {cell_h}, xstep: {xstep}')
res = find_instances_by_model(components, 'rhigh')
xstep = 0
cell_w = 0
y_offset = y_offset + hmax
hmax = 0
for item in res:
width = float(item['w'])*1e6
length = float(item['l'])*1e6
bends = int(item['b'])
m = int(item['m'])
for i in range(1,m+1):
pcell_decl = lib.layout().pcell_declaration('rhigh')
params = pcell_decl.params_as_hash(pcell_decl.get_parameters())
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), {'w': f'{width}u', 'l': f'{length}u', 'b' : f'{bends}'})
cell=layout.cell(pcell)
bbox=cell.bbox()
xstep = xstep + 100 + cell_w
top_cell.insert(klayout.db.CellInstArray(pcell, klayout.db.Trans(klayout.db.Point(xstep, y_offset))))
cell_w=bbox.width()
cell_h=bbox.height()
if cell_h > hmax:
hmax = cell_h
res = find_instances_by_model(components, 'rsil')
xstep = 0
cell_w = 0
y_offset = y_offset + hmax
hmax = 0
for item in res:
width = float(item['w'])*1e6
length = float(item['l'])*1e6
bends = int(item['b'])
m = int(item['m'])
for i in range(1,m+1):
pcell_decl = lib.layout().pcell_declaration('rsil')
params = pcell_decl.params_as_hash(pcell_decl.get_parameters())
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), {'w': f'{width}u', 'l': f'{length}u', 'b' : f'{bends}'})
cell=layout.cell(pcell)
bbox=cell.bbox()
xstep = xstep + 100 + cell_w
top_cell.insert(klayout.db.CellInstArray(pcell, klayout.db.Trans(klayout.db.Point(xstep,y_offset))))
cell_w=bbox.width()
cell_h=bbox.height()
if cell_h > hmax:
hmax = cell_h
mos = find_instances_by_model(components, 'sg13_lv_nmos')
xstep = 0
cell_w = 0
y_offset = y_offset + hmax
hmax = 0
for item in mos:
width = float(item['w'][:-1])
length = float(item['l'][:-1])
ng = int(item['ng'])
m = int(item['m'])
for i in range(1,m+1):
pcell_decl = lib.layout().pcell_declaration('nmos')
params = pcell_decl.params_as_hash(pcell_decl.get_parameters())
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), {'w': f'{width}u', 'l': f'{length}u', 'ng' : f'{ng}'})
cell=layout.cell(pcell)
bbox=cell.bbox()
xstep = xstep + 100 + cell_w
top_cell.insert(klayout.db.CellInstArray(pcell, klayout.db.Trans(klayout.db.Point(xstep, y_offset))))
cell_w=bbox.width()
cell_h=bbox.height()
if cell_h > hmax:
hmax = cell_h
mos = find_instances_by_model(components, 'sg13_hv_nmos')
xstep = 0
cell_w = 0
y_offset = y_offset + hmax
hmax = 0
for item in mos:
width = float(item['w'][:-1])
length = float(item['l'][:-1])
ng = int(item['ng'])
m = int(item['m'])
for i in range(1,m+1):
pcell_decl = lib.layout().pcell_declaration('nmosHV')
params = pcell_decl.params_as_hash(pcell_decl.get_parameters())
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), {'w': f'{width}u', 'l': f'{length}u', 'ng' : f'{ng}'})
cell=layout.cell(pcell)
bbox=cell.bbox()
xstep = xstep + 100 + cell_w
top_cell.insert(klayout.db.CellInstArray(pcell, klayout.db.Trans(klayout.db.Point(xstep, y_offset))))
cell_w=bbox.width()
cell_h=bbox.height()
if cell_h > hmax:
hmax = cell_h
mos = find_instances_by_model(components, 'sg13_lv_pmos')
xstep = 0
cell_w = 0
y_offset = y_offset + hmax
hmax = 0
for item in mos:
name = item['name']
width = float(item['w'][:-1])
length = float(item['l'][:-1])
ng = int(item['ng'])
m = int(item['m'])
for i in range(1,m+1):
pcell_decl = lib.layout().pcell_declaration('pmos')
params = pcell_decl.params_as_hash(pcell_decl.get_parameters())
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), {'w': f'{width}u', 'l': f'{length}u', 'ng' : f'{ng}'})
cell=layout.cell(pcell)
bbox=cell.bbox()
xstep = xstep + 100 + cell_w
top_cell.insert(klayout.db.CellInstArray(pcell, klayout.db.Trans(klayout.db.Point(xstep, y_offset))))
cell_w=bbox.width()
cell_h=bbox.height()
if cell_h > hmax:
hmax = cell_h
mos = find_instances_by_model(components, 'sg13_hv_pmos')
xstep = 0
cell_w = 0
y_offset = y_offset + hmax
hmax = 0
for item in mos:
name = item['name']
width = float(item['w'][:-1])
length = float(item['l'][:-1])
ng = int(item['ng'])
m = int(item['m'])
for i in range(1,m+1):
pcell_decl = lib.layout().pcell_declaration('pmosHV')
params = pcell_decl.params_as_hash(pcell_decl.get_parameters())
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), {'w': f'{width}u', 'l': f'{length}u', 'ng' : f'{ng}'})
cell=layout.cell(pcell)
bbox=cell.bbox()
xstep = xstep + 100 + cell_w
top_cell.insert(klayout.db.CellInstArray(pcell, klayout.db.Trans(klayout.db.Point(xstep, y_offset))))
cell_w=bbox.width()
cell_h=bbox.height()
if cell_h > hmax:
hmax = cell_h
cmim = find_instances_by_model(components, 'cap_cmim')
xstep = 0
cell_w = 0
y_offset = y_offset + hmax
hmax = 0
for item in cmim:
width = float(item['w'])*1e6
length = float(item['l'])*1e6
m = int(item['m'])
for i in range(1,m+1):
pcell_decl = lib.layout().pcell_declaration('cmim')
params = pcell_decl.params_as_hash(pcell_decl.get_parameters())
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), {'w': f'{width}u', 'l': f'{length}u'})
cell=layout.cell(pcell)
bbox=cell.bbox()
xstep = xstep + 100 + cell_w
top_cell.insert(klayout.db.CellInstArray(pcell, klayout.db.Trans(klayout.db.Point(xstep, y_offset))))
cell_w=bbox.width()
cell_h=bbox.height()
if cell_h > hmax:
hmax = cell_h
dant = find_instances_by_model(components, 'dantenna')
xstep = 0
cell_w = 0
y_offset = y_offset + hmax
hmax = 0
for item in dant:
name = item['name']
model = item['model']
width = float(item['w'][:-1])
length = float(item['l'][:-1])
pcell_decl = lib.layout().pcell_declaration('dantenna')
params = pcell_decl.params_as_hash(pcell_decl.get_parameters())
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), {'w': f'{width}u', 'l': f'{length}u'})
cell=layout.cell(pcell)
bbox=cell.bbox()
xstep = xstep + 100 + cell_w
top_cell.insert(klayout.db.CellInstArray(pcell, klayout.db.Trans(klayout.db.Point(xstep, y_offset))))
cell_w=bbox.width()
cell_h=bbox.height()
if cell_h > hmax:
hmax = cell_h
dant = find_instances_by_model(components, 'dpantenna')
xstep = 0
cell_w = 0
y_offset = y_offset + hmax
hmax = 0
for item in dant:
name = item['name']
model = item['model']
width = float(item['w'][:-1])
length = float(item['l'][:-1])
pcell_decl = lib.layout().pcell_declaration('dpantenna')
params = pcell_decl.params_as_hash(pcell_decl.get_parameters())
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), {'w': f'{width}u', 'l': f'{length}u'})
cell=layout.cell(pcell)
bbox=cell.bbox()
xstep = xstep + 100 + cell_w
top_cell.insert(klayout.db.CellInstArray(pcell, klayout.db.Trans(klayout.db.Point(xstep, y_offset))))
cell_w=bbox.width()
cell_h=bbox.height()
if cell_h > hmax:
hmax = cell_h
pathlib.Path(output).parent.mkdir(parents=True, exist_ok=True)
layout.write(output)
def find_instances_by_model(components, model_name):
return [item for item in components if item['model'] == model_name]
try:
netlist
except NameError:
print("Missing netlist argument. Please define '-rd netlsit=<path-to-netlist>'")
sys.exit(1)
# sys.exit(1)
try:
output
except NameError:
print("Missing output argument. Please define '-rd output=<path-to-sealring>'")
sys.exit(1)
def main():
with open(netlist, 'r') as file:
netlist_content = file.read()
generate_devices(netlist_content, output) # pylint: disable=undefined-variable
print(f'GDS created, open it using: ->klayout {output}')
if __name__ == "__main__":
main()

View File

@ -1,282 +0,0 @@
"""
Module to automatically generate a gallery of devices out of a SPICE netlist by creating a new GDS file.
Can be used in Klayout's batch mode. For example:
klayout -n sg13g2 -zz -r generator.py -rd netlist=netlist.spice -rd output=macros/gallery.gds
"""
import pathlib
import sys
import re
from typing import List, Dict
import pya
import klayout.db
LIB = 'SG13_dev'
def parse_netlist(netlist: str) -> List[Dict[str, str]]:
print("[DEBUG] Starting to parse netlist...")
# Define regular expression patterns for each component type
patterns = {
'ipin': re.compile(r"^\*\.ipin\s+(\S+)\s*$", re.MULTILINE),
'opin': re.compile(r"^\*\.opin\s+(\S+)\s*$", re.MULTILINE),
'iopin': re.compile(r"^\*\.iopin\s+(\S+)\s*$", re.MULTILINE),
'bjt': re.compile(
r"^XQ(\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)\s+Nx=([\d\.]+)(?:\s+le=([\d\.e-]+))?\s*$",
re.MULTILINE
),
'capacitor': re.compile(
r"^XC(\S+)\s+\S+\s+\S+\s+(\S+)\s+w=([\d\.e-]+)\s+l=([\d\.e-]+)\s+m=([\d\.]+)\s*$",
re.MULTILINE
),
'diode': re.compile(
r"^XD(\S+)\s+\S+\s+\S+\s+(\S+)\s+l=([\d\.a-zA-Z]+)\s+w=([\d\.a-zA-Z]+)\s*$",
re.MULTILINE
),
'resistor': re.compile(
r"^XR(\S+)\s+\S+\s+\S+\s+(\S+)\s+w=([\d\.e-]+)\s+l=([\d\.e-]+)\s+m=([\d\.]+)\s+b=([\d\.]+)\s*$",
re.MULTILINE
),
'mosfet': re.compile(
r"^XM(\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)\s+w=([\d\.u-]+)\s+l=([\d\.u-]+)\s+ng=([\d\.]+)\s+m=([\d\.]+)(?:\s+rfmode=([\d\.]+))?\s*$",
re.MULTILINE
),
'tap': re.compile(
r"^XR(\S+)\s+\S+\s+\S+\s+(\S+)\s*$",
re.MULTILINE
)
}
components = []
# Parse each type of component
for component_type, pattern in patterns.items():
matches = list(pattern.finditer(netlist))
print(f"[DEBUG] Found {len(matches)} '{component_type}' components.")
for match in matches:
component = {'type': component_type}
if component_type in ['ipin', 'opin', 'iopin']:
component['name'] = match.group(1)
component['model'] = component_type
elif component_type == 'bjt':
component['name'] = f"XQ{match.group(1)}"
component['model'] = match.group(2)
component['Nx'] = match.group(3)
component['le'] = match.group(4) if match.group(4) else None
elif component_type == 'capacitor':
component['name'] = f"XC{match.group(1)}"
component['model'] = match.group(2)
component['w'] = match.group(3)
component['l'] = match.group(4)
component['m'] = match.group(5)
elif component_type == 'diode':
component['name'] = f"XD{match.group(1)}"
component['model'] = match.group(2)
component['l'] = match.group(3)
component['w'] = match.group(4)
elif component_type == 'resistor':
component['name'] = f"XR{match.group(1)}"
component['model'] = match.group(2)
component['w'] = match.group(3)
component['l'] = match.group(4)
component['m'] = match.group(5)
component['b'] = match.group(6)
elif component_type == 'mosfet':
component['name'] = f"XM{match.group(1)}"
component['model'] = match.group(2)
component['w'] = match.group(3)
component['l'] = match.group(4)
component['ng'] = match.group(5)
component['m'] = match.group(6)
component['rfmode'] = match.group(7) if match.group(7) else None
elif component_type == 'tap':
component['name'] = f"XR{match.group(1)}"
component['model'] = match.group(2) # Assuming group 2 is the model
else:
print(f"[WARNING] Unknown component type: {component_type}")
components.append(component)
print(f"[DEBUG] Parsed component: {component}")
print(f"[DEBUG] Total components parsed: {len(components)}")
return components
def find_instances_by_model(components: List[Dict[str, str]], model_name: str) -> List[Dict[str, str]]:
print(f"[DEBUG] Filtering components for model: '{model_name}'")
filtered = [comp for comp in components if comp.get('model') == model_name]
print(f"[DEBUG] Found {len(filtered)} instances of model: '{model_name}'")
return filtered
def generate_devices(netlist: str, output: str):
print("[DEBUG] Starting device generation...")
components = parse_netlist(netlist)
print("[DEBUG] Completed netlist parsing.")
lib = pya.Library.library_by_name(LIB)
if lib is None:
print(f"[ERROR] Library '{LIB}' not found. Please ensure it is loaded in KLayout.")
sys.exit(1)
print(f"[DEBUG] Using library: '{LIB}'")
layout = klayout.db.Layout(True)
layout.dbu = 0.001
print(f"[DEBUG] Layout created with dbu={layout.dbu}")
top_cell = layout.cell(layout.add_cell('gallery'))
print("[DEBUG] Created top-level cell 'gallery'.")
# Define the order and models to process
models_to_process = [
'rppd', 'rhigh', 'rsil',
'sg13_lv_nmos', 'sg13_hv_nmos',
'sg13_lv_pmos', 'sg13_hv_pmos',
'cap_cmim', 'dantenna', 'dpantenna'
]
# Initialize positioning variables
xstep = 0
y_offset = 100 # Starting y offset
hmax = 0 # Maximum height of the current row
for model_name in models_to_process:
print(f"[DEBUG] Processing model: '{model_name}'")
res = find_instances_by_model(components, model_name)
if not res:
print(f"[DEBUG] No instances found for model: '{model_name}'. Skipping.")
continue
for item in res:
print(f"[DEBUG] Processing item: {item}")
try:
# Handle different models with varying parameters
if model_name in ['rppd', 'rhigh', 'rsil']:
width = float(item['w']) * 1e6
length = float(item['l']) * 1e6
bends = int(item['b'])
m = int(item['m'])
pcell_type = model_name
params = {
'w': f'{width}u',
'l': f'{length}u',
'b': f'{bends}'
}
elif model_name in ['sg13_lv_nmos', 'sg13_hv_nmos', 'sg13_lv_pmos', 'sg13_hv_pmos']:
width = float(item['w'].rstrip('u')) # Remove 'u' if present
length = float(item['l'].rstrip('u'))
ng = int(item['ng'])
m = int(item['m'])
pcell_type = 'nmos' if 'nmos' in model_name else 'pmos'
if 'HV' in model_name:
pcell_type += 'HV'
params = {
'w': f'{width}u',
'l': f'{length}u',
'ng': f'{ng}'
}
elif model_name in ['cap_cmim']:
width = float(item['w']) * 1e6
length = float(item['l']) * 1e6
m = int(item['m'])
pcell_type = model_name
params = {
'w': f'{width}u',
'l': f'{length}u'
}
elif model_name in ['dantenna', 'dpantenna']:
width = float(item['w'].rstrip('u'))
length = float(item['l'].rstrip('u'))
pcell_type = model_name
params = {
'w': f'{width}u',
'l': f'{length}u'
}
else:
print(f"[WARNING] Unhandled model type: '{model_name}'. Skipping.")
continue
for i in range(1, m + 1):
print(f"[DEBUG] Creating PCell variant for '{pcell_type}' with params: {params}")
try:
pcell_decl = lib.layout().pcell_declaration(pcell_type)
if pcell_decl is None:
print(f"[ERROR] PCell declaration for '{pcell_type}' not found in library '{LIB}'.")
continue
pcell = layout.add_pcell_variant(lib, pcell_decl.id(), params)
cell = layout.cell(pcell)
bbox = cell.bbox()
print(f"[DEBUG] Bounding box for PCell '{pcell_type}': {bbox}")
# Positioning logic
xstep += 100 + bbox.width()
print(f"[DEBUG] Updated xstep: {xstep}")
# Insert the PCell into the top cell
top_cell.insert(klayout.db.CellInstArray(
pcell,
klayout.db.Trans(klayout.db.Point(xstep, y_offset))
))
print(f"[DEBUG] Inserted '{pcell_type}' PCell at position ({xstep}, {y_offset})")
# Update the maximum height for the current row
cell_h = bbox.height()
if cell_h > hmax:
hmax = cell_h
print(f"[DEBUG] Updated hmax: {hmax}")
except Exception as e:
print(f"[ERROR] Exception while creating PCell for '{pcell_type}': {e}")
# After processing each model, update y_offset for the next row
y_offset += hmax
hmax = 0 # Reset for the next row
xstep = 0 # Reset xstep for the next row
print(f"[DEBUG] Updated y_offset: {y_offset}")
# Ensure output directory exists
try:
pathlib.Path(output).parent.mkdir(parents=True, exist_ok=True)
print(f"[DEBUG] Output directory '{pathlib.Path(output).parent}' is ready.")
except Exception as e:
print(f"[ERROR] Failed to create output directory: {e}")
sys.exit(1)
# Write the layout to the output GDS file
try:
layout.write(output)
print(f"[DEBUG] Layout successfully written to '{output}'.")
except Exception as e:
print(f"[ERROR] Failed to write layout to '{output}': {e}")
sys.exit(1)
def main():
# Check for required arguments
try:
netlist
except NameError:
print("Missing netlist argument. Please define '-rd netlist=<path-to-netlist>'")
sys.exit(1)
try:
output
except NameError:
print("Missing output argument. Please define '-rd output=<path-to-output-gds>'")
sys.exit(1)
print("[DEBUG] Opening netlist file...")
# Read the netlist content
try:
with open(netlist, 'r') as file:
netlist_content = file.read()
print(f"[DEBUG] Successfully read netlist file: '{netlist}'")
except Exception as e:
print(f"[ERROR] Failed to read netlist file '{netlist}': {e}")
sys.exit(1)
# Generate devices and create GDS
generate_devices(netlist_content, output)
print(f'[INFO] GDS file created successfully. Open it using: klayout {output}')
if __name__ == "__main__":
main()