Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tiarnach/sweep #15

Merged
merged 14 commits into from
Jul 22, 2023
12 changes: 8 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
*.pyc
*.log
dist
dist/
__pycache__
build
build/
/MATLAB
src/**/*.png
.venv
venv
pygmid.egg-info
.pytest_cache
.vscode
.pytest_cache/
.vscode/
sweep.*/
*.pkl
*.scs
config.cfg
6 changes: 4 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[metadata]
name = pygmid
version = attr: pygmid.__version__
author = Cian O`Donnell, Danylo Galach, Tiarnach Ó Riada
author = Cian O`Donnell, Tiarnach Ó Riada, Danylo Galach
author_email = cian.odonnell@tyndall.ie
description = A python 3 implementation of the gm/ID starter kit
long_description = file: README.md, LICENSE
Expand All @@ -22,10 +22,12 @@ setup_requires =
numpy
scipy
matplotlib
psf_utils
install_requires =
numpy
scipy
matplotlib
matplotlib
psf_utils

[options.packages.find]
where = src
37 changes: 15 additions & 22 deletions src/pygmid/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,28 @@

from .version import __version__
from . import pygmid
from . import sweep

def _cli():
from argparse import ArgumentParser
description = "CLI for pygmid. Run techsweeps"
parser = ArgumentParser(description=description)
parser.add_argument('--version', action='version', version=f"%prog {__version__}")
parser.add_argument('-c', action='store_const', dest='constant_value',
const='value-to-store',
help='Store a constant value')
parser.add_argument('--mode', choices=['lookup', 'sweep'], default='lookup')
parser.add_argument('--config', type=str)

results = parser.parse_args()
# assign values from results here
# val = results.constant_value

#if opt.backend is not None:
# zookeeper.backend = opt.backend
#if opt.configfile is not None:
# zookeeper.configfile = opt.configfile

#if len(remaining_args) != 0:
# print("Usage: python -m zookeeper [settings] \npython -m zookeeper -h for help")
# sys.exit(1)

# setup logger
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

pygmid.main()
sys.exit(0)
args = parser.parse_args()
if args.mode == 'sweep':
if args.config is None:
logging.error('Please provide a config file with --config if using the sweep mode')
sys.exit(-1)
sweep.run(args.config)
sys.exit(0)
elif args.mode == 'lookup':
pygmid.main()
sys.exit(0)
sys.exit(-1)

if __name__ == '__main__':
# setup logger here
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
_cli()
3 changes: 3 additions & 0 deletions src/pygmid/sweep/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .__main__ import run

__all__ = ['run']
10 changes: 10 additions & 0 deletions src/pygmid/sweep/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import sys

from .sweep import Sweep

def run(config_file_path: str) -> (str, str):
swp = Sweep(config_file_path)
return swp.run()

if __name__ == '__main__':
run(str(sys.argv[1]))
156 changes: 156 additions & 0 deletions src/pygmid/sweep/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import numpy as np
import json
import ast
import configparser

def matrange(start, step, stop):
num = round((stop - start) / step + 1)

return np.linspace(start, stop, num)

class Config:
def __init__(self, config_file_path: str):
self._configParser = configparser.ConfigParser()
self._configParser.optionxform = lambda option: option.upper()
self._configParser.read(config_file_path)
self._config = {s:dict(self._configParser.items(s)) for s in self._configParser.sections()}
self._parse_ranges()
self._generate_netlist()

n = []
p = []
self._config['outvars'] = ['ID','VT','IGD','IGS','GM','GMB','GDS','CGG','CGS','CSG','CGD','CDG','CGB','CDD','CSS']
n.append( ['mn:ids','A', [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
n.append( ['mn:vth','V', [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
n.append( ['mn:igd','A', [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
n.append( ['mn:igs','A', [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
n.append( ['mn:gm','S', [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
n.append( ['mn:gmbs','S', [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
n.append( ['mn:gds','S', [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 ]])
n.append( ['mn:cgg','F', [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]])
n.append( ['mn:cgs','F', [0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0 ]])
n.append( ['mn:cgd','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0 ]])
n.append( ['mn:cgb','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0 ]])
n.append( ['mn:cdd','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 ]])
n.append( ['mn:cdg','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0 ]])
n.append( ['mn:css','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ]])
n.append( ['mn:csg','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0 ]])
n.append( ['mn:cjd','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 ]])
n.append( ['mn:cjs','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ]])
self._config['n'] = n

p.append( ['mp:ids','A', [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
p.append( ['mp:vth','V', [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
p.append( ['mp:igd','A', [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
p.append( ['mp:igs','A', [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
p.append( ['mp:gm','S', [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
p.append( ['mp:gmbs','S', [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]])
p.append( ['mp:gds','S', [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 ]])
p.append( ['mp:cgg','F', [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ]])
p.append( ['mp:cgs','F', [0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0 ]])
p.append( ['mp:cgd','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0 ]])
p.append( ['mp:cgb','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0 ]])
p.append( ['mp:cdd','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 ]])
p.append( ['mp:cdg','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0 ]])
p.append( ['mp:css','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ]])
p.append( ['mp:csg','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0 ]])
p.append( ['mp:cjd','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 ]])
p.append( ['mp:cjs','F', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ]])
self._config['p'] = p

self._config['outvars_noise'] = ['STH','SFL']
n_noise = []
p_noise = []
n_noise.append(['mn:id', ''])
n_noise.append(['mn:fn', ''])
self._config['n_noise'] = n_noise

p_noise.append(['mp:id', ''])
p_noise.append(['mp:fn', ''])
self._config['p_noise'] = p_noise

def __getitem__(self, key):
if key not in self._config.keys():
raise ValueError(f"Lookup table does not contain this data")

return self._config[key]

def _parse_ranges(self):
# parse numerical ranges
for k in ['VGS', 'VDS', 'VSB', 'LENGTH']:
v = ast.literal_eval(self._config['SWEEP'][k])
v = [v] if type(v) is not list else v
v = [matrange(*r) for r in v]
v = [val for r in v for val in r]
self._config['SWEEP'][k] = v

for k in ['WIDTH', 'NFING']:
self._config['SWEEP'][k] = int(self._config['SWEEP'][k])

def generate_m_dict(self):
return {
'INFO' : self._config['MODEL']['INFO'],
'CORNER' : self._config['MODEL']['CORNER'],
'TEMP' : self._config['MODEL']['TEMP'],
'NFING' : self._config['SWEEP']['NFING'],
'L' : np.array(self._config['SWEEP']['LENGTH']).T,
'W' : self._config['SWEEP']['WIDTH'],
'VGS' : np.array(self._config['SWEEP']['VGS']).T,
'VDS' : np.array(self._config['SWEEP']['VDS']).T,
'VSB' : np.array(self._config['SWEEP']['VSB']).T
}

def _generate_netlist(self):
modelfile = self._config['MODEL']['FILE']
paramfile = self._config['MODEL']['PARAMFILE']
width = self._config['SWEEP']['WIDTH']
modelp = self._config['MODEL']['MODELP']
modeln = self._config['MODEL']['MODELN']
try:
mn_supplement = '\n\t'.join(json.loads(self._config['MODEL']['MN']))
except json.decoder.JSONDecodeError:
raise "Error parsing config: make sure MN has no weird characters in it, and that the list isn't terminated with a trailing ','"
try:
mp_supplement = '\n\t'.join(json.loads(self._config['MODEL']['MP']))
except json.decoder.JSONDecodeError:
raise "Error parsing config: make sure MP has no weird characters in it, and that the list isn't terminated with a trailing ','"
temp =int(self._config['MODEL']['TEMP'])-273
VDS_max = max(self._config['SWEEP']['VDS'])
VDS_step = self._config['SWEEP']['VDS'][1] - self._config['SWEEP']['VDS'][0]
VGS_max = max(self._config['SWEEP']['VGS'])
VGS_step = self._config['SWEEP']['VGS'][1] - self._config['SWEEP']['VGS'][0]

NFING = self._config['SWEEP']['NFING']

netlist = (
f"//pysweep.scs",
f"include {modelfile}",
f'include "{paramfile}"\n',
f'save mn:oppoint',
f'save mp:oppoint',
f'\n',
f'parameters gs=0.498 ds=0.2 L=length*1e-6 Wtot={width}e-6 W=500n',
f'\n',
f'vnoi (vx 0) vsource dc=0',
f'vdsn (vdn vx) vsource dc=ds',
f'vgsn (vgn 0) vsource dc=gs',
f'vbsn (vbn 0) vsource dc=-sb',
f'vdsp (vdp vx) vsource dc=-ds',
f'vgsp (vgp 0) vsource dc=-gs',
f'vbsp (vbp 0) vsource dc=sb',
f'\n',
f'\n',
f'mp (vdp vgp 0 vbp) {modelp} {mp_supplement}',
f'\n',
f'mn (vdn vgn 0 vbn) {modeln} {mn_supplement}',
f'\n',
f'simulatorOptions options gmin=1e-13 reltol=1e-4 vabstol=1e-6 iabstol=1e-10 temp={temp} tnom=27',
f'sweepvds sweep param=ds start=0 stop={VDS_max} step={VDS_step} {{',
f'sweepvgs dc param=gs start=0 stop={VGS_max} step={VGS_step}',
f'}}',
f'sweepvds_noise sweep param=ds start=0 stop={VDS_max} step={VDS_step} {{',
f' sweepvgs_noise noise freq=1 oprobe=vnoi param=gs start=0 stop={VGS_max} step={VGS_step}',
f'}}'
)
with open('pysweep.scs', 'w') as outfile:
outfile.write('\n'.join(netlist))
8 changes: 8 additions & 0 deletions src/pygmid/sweep/pysweep.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import sys

from .sweep import Sweep

if __name__ == '__main__':
configfile = str(sys.argv[1])
swp = Sweep(configfile)
swp.run()
15 changes: 15 additions & 0 deletions src/pygmid/sweep/simulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import subprocess
import logging
class SpectreSimulator:
def __init__(self, *args):
self.__args = args

def run(self, filename: str):
infile = filename
try:
cmd_args = ['spectre', filename] + [*self.__args]
cp = subprocess.run(cmd_args, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except subprocess.CalledProcessError as e:
logging.info(f"Error executing process\n\n{e}")
return
logging.info(cp.stdout)
Loading
Loading