Module serafin.serafin
Single-Ended Operational Amplifier Characterization
Expand source code
""" Single-Ended Operational Amplifier Characterization """
from functools import reduce
from operator import or_
from shutil import rmtree
from typing import NamedTuple
import warnings
import errno
import yaml
import numpy as np
import pandas as pd
import pyspectre as ps
from .util import *
class OperationalAmplifier(NamedTuple):
"""
Single Ended Operational Amplifier
"""
session: ps.Session
sim_dir: str
parameters: dict[str, float]
geom_init: dict[str, float]
area_expr: str
constraints: dict[str, dict[str,float]]
devices: dict[str, str]
dcop_params: dict[str, str]
offs_params: dict[str, str]
performances: dict[str, str]
def __del__(self):
ps.stop_session(self.session, remove_raw = True)
rmtree(self.sim_dir)
def operational_amplifier( pdk_cfg: str, ckt_cfg: str, net: str
) -> OperationalAmplifier:
with open(ckt_cfg, mode = 'r', encoding = 'utf-8') as ckt_h:
ckt = yaml.load(ckt_h, Loader=yaml.FullLoader)
with open(pdk_cfg, mode = 'r', encoding = 'utf-8') as pdk_h:
pdk = yaml.load(pdk_h, Loader=yaml.FullLoader)
sim_dir = setup_dir(pdk, net, ckt)
net_path = f'{sim_dir}/tb.scs'
raw_path = f'{sim_dir}/op.raw'
session = ps.start_session(net_path, raw_path = raw_path)
parameters = pdk['defaults'] \
| pdk['testbench'] \
| ckt['parameters']['testbench']
geom_init = ckt['parameters']['geometrical']
area_expr = ckt['parameters']['area']
constraints = pdk['constraints']
devices = { d['id']: d['type'] for d in ckt['devices'] }
dc_opps = pdk['devices']['dcop']['parameters']
op_pre = pdk['devices']['dcop']['prefix']
op_suf = pdk['devices']['dcop']['suffix']
dcop_params = dict( reduce( or_
, [ { f'{op_pre}{d}{op_suf}:{op}': f'{d}_{op}'
for op in dc_opps }
for d,t in devices.items()
if t not in ["CAP", "RES"] ] ) )
of_devs = pdk['devices']['dcmatch']
offs_params = dict( reduce( or_
, [ { f'{of["prefix"]}{d}{of["suffix"]}' : f'{d}_{of["reference"]}'
for of in of_devs[t] }
for d,t in devices.items()
if t not in ["CAP", "RES"] ] ) )
op_amp = OperationalAmplifier( session = session
, sim_dir = sim_dir
, parameters = parameters
, geom_init = geom_init
, area_expr = area_expr
, constraints = constraints
, devices = devices
, dcop_params = dcop_params
, offs_params = offs_params
, performances = PERFORMANCE_PARAMETERS
, )
ps.set_parameters(session, parameters | geom_init)
return op_amp
def initial_sizing(op: OperationalAmplifier) -> pd.DataFrame:
return from_dict(op.geom_init)
def random_sizing(op: OperationalAmplifier) -> pd.DataFrame:
keys = list(op.geom_init.keys())
l_ids = [k for k in keys if k.startswith('L')]
w_ids = [k for k in keys if k.startswith('W')]
m_ids = [k for k in keys if k.startswith('M')]
l_num = len(l_ids)
w_num = len(w_ids)
m_num = len(m_ids)
l_min = op.constraints['length']['min']
l_max = op.constraints['length']['max']
l_grid = op.constraints['length']['grid']
ls = np.random.choice(np.arange(l_min, l_max, l_grid), (1, l_num))
w_min = op.constraints['width']['min']
w_max = op.constraints['width']['max']
w_grid = op.constraints['width']['grid']
ws = np.random.choice(np.arange(w_min, w_max, w_grid), (1, w_num))
m_min = 1
m_max = 20
m_grid = 1
ms = np.random.choice(np.arange(m_min, m_max, m_grid), (1, m_num))
sizing = pd.DataFrame( np.hstack((ls, ws, ms))
, columns = (l_ids + w_ids + m_ids))
return sizing
def _current_sizing(op: OperationalAmplifier) -> dict[str,float]:
"""
Get current sizing parameters from netlist in active session as dict
"""
return ps.get_parameters(op.session, list(op.geom_init.keys()))
def current_sizing(op: OperationalAmplifier) -> pd.DataFrame:
"""
Get current sizing parameters from netlist in active session
"""
return from_dict(_current_sizing(op))
def _set_paramters( op: OperationalAmplifier, sizing: pd.DataFrame
, tol: float = 1e-10 ) -> bool:
cols = sorted(list([c for c in sizing.columns
if c.startswith('W')
or c.startswith('L')
or c.startswith('M') ]))
sloc = [c for c in sizing.columns if c not in cols]
lmin = op.constraints['length']['min']
lmax = op.constraints['length']['max']
wmin = op.constraints['width']['min']
wmax = op.constraints['width']['max']
mmin = 1
mmax = 42
mins = np.array([ lmin if c.startswith('L') else
( wmin if c.startswith('W') else
mmin ) for c in cols ])
maxs = np.array([ lmax if c.startswith('L') else
( wmax if c.startswith('W') else
mmax ) for c in cols ])
vals = np.clip(sizing[cols].values[0], mins, maxs)[None,:]
sizes = pd.DataFrame(vals, columns = cols).join(sizing[sloc])
ret = ps.set_parameters(op.session, to_dict(sizes))
if not ret:
msg = f'spectre failed to set sizing parameters with non-zero exit code {ret}.'
raise(IOError(errno.EIO, os.strerror(errno.EIO), msg))
return np.all(np.abs(sizes[cols].values - sizing[cols].values) < tol)
def evaluate( op: OperationalAmplifier, sizing: pd.DataFrame = None
, dcop_only: bool = False ) -> pd.DataFrame:
"""
Evaluate the operational amplifier with a given sizing. Optionally only
determines the operating point, which is faster.
"""
if sizing is not None:
ret = _set_paramters(op, sizing)
if not ret:
warnings.warn('Clipped sizing', RuntimeWarning)
if dcop_only:
_ = [ps.run_analysis(op.session, f'fb{fb}') for fb in range(1,7)]
results = ps.run_analysis(op.session, 'dcop')
else:
results = ps.run_all(op.session)
if not bool(results):
msg = 'Simulations Failed.'
raise(IOError(errno.EIO, os.strerror(errno.EIO), msg))
if dcop_only:
perf = operating_point(results['dcop'], op.dcop_params)
else:
perf = extract_performance(op, results)
return perf
def extract_performance( op: OperationalAmplifier
, results: dict[str, pd.DataFrame]
) -> pd.DataFrame:
"""
Extract performance from simulation results
"""
vdd = op.parameters['vdd']
dev = op.parameters['dev']
dcmatch = offset(results['dcmatch'], op.offs_params)
stb = stability(results['stb'])
tran = transient(results['tran'])
noise = output_referred_noise(results['noise'])
dc1 = out_swing_dc(results['dc1'], vdd, dev)
ac = out_swing_ac(results['ac'], stb['a_0'].values.item() - 3.0, vdd)
xf = rejection(results['xf'])
dc34 = output_current(results['dc3'], results['dc4'])
dcop = operating_point(results['dcop'], op.dcop_params)
area = estimated_area(op.area_expr, current_sizing(op))
perf = pd.concat( [ area
, dcmatch
, stb
, tran
, noise
, dc1
, ac
, xf
, dc34
, dcop ]
, axis = 1 )
return perf
Functions
def current_sizing(op: OperationalAmplifier) ‑> pandas.core.frame.DataFrame
-
Get current sizing parameters from netlist in active session
Expand source code
def current_sizing(op: OperationalAmplifier) -> pd.DataFrame: """ Get current sizing parameters from netlist in active session """ return from_dict(_current_sizing(op))
def evaluate(op: OperationalAmplifier, sizing: pandas.core.frame.DataFrame = None, dcop_only: bool = False) ‑> pandas.core.frame.DataFrame
-
Evaluate the operational amplifier with a given sizing. Optionally only determines the operating point, which is faster.
Expand source code
def evaluate( op: OperationalAmplifier, sizing: pd.DataFrame = None , dcop_only: bool = False ) -> pd.DataFrame: """ Evaluate the operational amplifier with a given sizing. Optionally only determines the operating point, which is faster. """ if sizing is not None: ret = _set_paramters(op, sizing) if not ret: warnings.warn('Clipped sizing', RuntimeWarning) if dcop_only: _ = [ps.run_analysis(op.session, f'fb{fb}') for fb in range(1,7)] results = ps.run_analysis(op.session, 'dcop') else: results = ps.run_all(op.session) if not bool(results): msg = 'Simulations Failed.' raise(IOError(errno.EIO, os.strerror(errno.EIO), msg)) if dcop_only: perf = operating_point(results['dcop'], op.dcop_params) else: perf = extract_performance(op, results) return perf
def extract_performance(op: OperationalAmplifier, results: dict[str, pandas.core.frame.DataFrame]) ‑> pandas.core.frame.DataFrame
-
Extract performance from simulation results
Expand source code
def extract_performance( op: OperationalAmplifier , results: dict[str, pd.DataFrame] ) -> pd.DataFrame: """ Extract performance from simulation results """ vdd = op.parameters['vdd'] dev = op.parameters['dev'] dcmatch = offset(results['dcmatch'], op.offs_params) stb = stability(results['stb']) tran = transient(results['tran']) noise = output_referred_noise(results['noise']) dc1 = out_swing_dc(results['dc1'], vdd, dev) ac = out_swing_ac(results['ac'], stb['a_0'].values.item() - 3.0, vdd) xf = rejection(results['xf']) dc34 = output_current(results['dc3'], results['dc4']) dcop = operating_point(results['dcop'], op.dcop_params) area = estimated_area(op.area_expr, current_sizing(op)) perf = pd.concat( [ area , dcmatch , stb , tran , noise , dc1 , ac , xf , dc34 , dcop ] , axis = 1 ) return perf
def initial_sizing(op: OperationalAmplifier) ‑> pandas.core.frame.DataFrame
-
Expand source code
def initial_sizing(op: OperationalAmplifier) -> pd.DataFrame: return from_dict(op.geom_init)
def operational_amplifier(pdk_cfg: str, ckt_cfg: str, net: str) ‑> OperationalAmplifier
-
Expand source code
def operational_amplifier( pdk_cfg: str, ckt_cfg: str, net: str ) -> OperationalAmplifier: with open(ckt_cfg, mode = 'r', encoding = 'utf-8') as ckt_h: ckt = yaml.load(ckt_h, Loader=yaml.FullLoader) with open(pdk_cfg, mode = 'r', encoding = 'utf-8') as pdk_h: pdk = yaml.load(pdk_h, Loader=yaml.FullLoader) sim_dir = setup_dir(pdk, net, ckt) net_path = f'{sim_dir}/tb.scs' raw_path = f'{sim_dir}/op.raw' session = ps.start_session(net_path, raw_path = raw_path) parameters = pdk['defaults'] \ | pdk['testbench'] \ | ckt['parameters']['testbench'] geom_init = ckt['parameters']['geometrical'] area_expr = ckt['parameters']['area'] constraints = pdk['constraints'] devices = { d['id']: d['type'] for d in ckt['devices'] } dc_opps = pdk['devices']['dcop']['parameters'] op_pre = pdk['devices']['dcop']['prefix'] op_suf = pdk['devices']['dcop']['suffix'] dcop_params = dict( reduce( or_ , [ { f'{op_pre}{d}{op_suf}:{op}': f'{d}_{op}' for op in dc_opps } for d,t in devices.items() if t not in ["CAP", "RES"] ] ) ) of_devs = pdk['devices']['dcmatch'] offs_params = dict( reduce( or_ , [ { f'{of["prefix"]}{d}{of["suffix"]}' : f'{d}_{of["reference"]}' for of in of_devs[t] } for d,t in devices.items() if t not in ["CAP", "RES"] ] ) ) op_amp = OperationalAmplifier( session = session , sim_dir = sim_dir , parameters = parameters , geom_init = geom_init , area_expr = area_expr , constraints = constraints , devices = devices , dcop_params = dcop_params , offs_params = offs_params , performances = PERFORMANCE_PARAMETERS , ) ps.set_parameters(session, parameters | geom_init) return op_amp
def random_sizing(op: OperationalAmplifier) ‑> pandas.core.frame.DataFrame
-
Expand source code
def random_sizing(op: OperationalAmplifier) -> pd.DataFrame: keys = list(op.geom_init.keys()) l_ids = [k for k in keys if k.startswith('L')] w_ids = [k for k in keys if k.startswith('W')] m_ids = [k for k in keys if k.startswith('M')] l_num = len(l_ids) w_num = len(w_ids) m_num = len(m_ids) l_min = op.constraints['length']['min'] l_max = op.constraints['length']['max'] l_grid = op.constraints['length']['grid'] ls = np.random.choice(np.arange(l_min, l_max, l_grid), (1, l_num)) w_min = op.constraints['width']['min'] w_max = op.constraints['width']['max'] w_grid = op.constraints['width']['grid'] ws = np.random.choice(np.arange(w_min, w_max, w_grid), (1, w_num)) m_min = 1 m_max = 20 m_grid = 1 ms = np.random.choice(np.arange(m_min, m_max, m_grid), (1, m_num)) sizing = pd.DataFrame( np.hstack((ls, ws, ms)) , columns = (l_ids + w_ids + m_ids)) return sizing
Classes
class OperationalAmplifier (session: pyspectre.core.Session, sim_dir: str, parameters: dict[str, float], geom_init: dict[str, float], area_expr: str, constraints: dict[str, dict[str, float]], devices: dict[str, str], dcop_params: dict[str, str], offs_params: dict[str, str], performances: dict[str, str])
-
Single Ended Operational Amplifier
Expand source code
class OperationalAmplifier(NamedTuple): """ Single Ended Operational Amplifier """ session: ps.Session sim_dir: str parameters: dict[str, float] geom_init: dict[str, float] area_expr: str constraints: dict[str, dict[str,float]] devices: dict[str, str] dcop_params: dict[str, str] offs_params: dict[str, str] performances: dict[str, str] def __del__(self): ps.stop_session(self.session, remove_raw = True) rmtree(self.sim_dir)
Ancestors
- builtins.tuple
Instance variables
var area_expr : str
-
Alias for field number 4
var constraints : dict[str, dict[str, float]]
-
Alias for field number 5
var dcop_params : dict[str, str]
-
Alias for field number 7
var devices : dict[str, str]
-
Alias for field number 6
var geom_init : dict[str, float]
-
Alias for field number 3
var offs_params : dict[str, str]
-
Alias for field number 8
var parameters : dict[str, float]
-
Alias for field number 2
var performances : dict[str, str]
-
Alias for field number 9
var session : pyspectre.core.Session
-
Alias for field number 0
var sim_dir : str
-
Alias for field number 1