# BSD 3-Clause License
#
# Copyright (c) 2018, Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from typing import Type, Union, Optional, Any, cast, Sequence, Mapping, Tuple
from pathlib import Path
from shutil import copy
import numpy as np
import matplotlib.pyplot as plt
from bag.simulation.measure import MeasurementManager, MeasInfo
from bag.simulation.core import TestbenchManager
from bag.simulation.data import SimNetlistInfo, netlist_info_from_dict
from bag.simulation.cache import SimulationDB, DesignInstance, SimResults, MeasureResult
from bag.design.module import Module
from bag.concurrent.util import GatherHelper
from bag.math import float_to_si_string
from ...schematic.char_tb_sp import bag3_testbenches__char_tb_sp
[docs]class CharSPTB(TestbenchManager):
@classmethod
[docs] def get_schematic_class(cls) -> Type[Module]:
return bag3_testbenches__char_tb_sp
[docs] def get_netlist_info(self) -> SimNetlistInfo:
sweep_var: str = self.specs.get('sweep_var', 'freq')
sweep_options: Mapping[str, Any] = self.specs['sweep_options']
sp_options: Mapping[str, Any] = self.specs.get('sp_options', {})
save_outputs: Sequence[str] = self.specs.get('save_outputs', ['plus', 'minus'])
sp_dict = dict(type='SP',
param=sweep_var,
sweep=sweep_options,
param_type='Y',
ports=['PORTP', 'PORTM'],
options=sp_options,
save_outputs=save_outputs,
)
sim_setup = self.get_netlist_info_dict()
sim_setup['analyses'] = [sp_dict]
return netlist_info_from_dict(sim_setup)
[docs]class CharSPMeas(MeasurementManager):
[docs] async def async_meas_case(self, name: str, sim_dir: Path, sim_db: SimulationDB, dut: Optional[DesignInstance],
sim_env: str, ibias: float = 0.0) -> Mapping[str, Any]:
tbm_specs = dict(
**self.specs['tbm_specs']['sp_meas'],
sim_envs=[sim_env],
)
tbm_specs['sim_params']['idc'] = ibias
tbm = cast(CharSPTB, self.make_tbm(CharSPTB, tbm_specs))
tbm_name = f'{name}_{float_to_si_string(ibias)}'
sim_dir = sim_dir / tbm_name
passive_type: str = self.specs['passive_type']
if passive_type == 'ind':
ind_specs: Mapping[str, Any] = self.specs['ind_specs']
sp_file = Path(ind_specs['sp_file'])
ind_sp = sp_file.name
sim_dir.mkdir(parents=True, exist_ok=True)
copy(sp_file, sim_dir / ind_sp)
tb_ind_specs = {'ind_sp': ind_sp, 'plus': ind_specs['plus'], 'minus': ind_specs['minus']}
dut_plus = 'plus'
dut_minus = 'minus'
else:
tb_ind_specs = None
dut_plus = self.specs['tbm_specs']['dut_plus']
dut_minus = self.specs['tbm_specs']['dut_minus']
tb_params = dict(
extracted=self.specs['tbm_specs'].get('extracted', True),
dut_plus=dut_plus,
dut_minus=dut_minus,
dut_vdd=self.specs['tbm_specs'].get('dut_vdd', 'VDD'),
dut_vss=self.specs['tbm_specs'].get('dut_vss', 'VSS'),
passive_type=passive_type,
ind_specs=tb_ind_specs,
)
sim_results = await sim_db.async_simulate_tbm_obj(tbm_name, sim_dir, dut, tbm,
tb_params=tb_params)
data = sim_results.data
return dict(freq=data['freq'], y11=np.squeeze(data['y11']), y12=np.squeeze(data['y12']),
y21=np.squeeze(data['y21']), y22=np.squeeze(data['y22']))
[docs]def estimate_cap(freq: np.ndarray, yc: np.ndarray) -> float:
"""assume yc = jwC"""
fit = np.polyfit(2 * np.pi * freq, np.imag(yc), 1)
return fit[0]
[docs]def estimate_ind(freq: np.ndarray, zc: np.ndarray) -> Mapping[str, float]:
"""assume res and ind in series; cap in parallel"""
w = 2 * np.pi * freq
# find SRF: min freq where zc.imag goes from positive to negative
vec = (zc.imag >= 0).astype(int)
dvec = np.diff(vec)
dvec = np.minimum(dvec, 0)
loc1 = dvec.nonzero()[0][0]
# Linearly interpolate between loc and (loc + 1)
w1, z1 = w[loc1], zc.imag[loc1]
w2, z2 = w[loc1 + 1], zc.imag[loc1 + 1]
w0 = w2 - z2 * (w2 - w1) / (z2 - z1)
# upto 0.1 * SRF, assume zc is just R + jwL
idx0 = np.where(w < 0.1 * w0)[0][-1]
res = np.mean(zc.real[:idx0])
_fit = np.polyfit(w[:idx0], zc.imag[:idx0], 1)
ind = _fit[0]
cap = 1 / (w0 * w0 * ind)
ans = dict(ind=ind, res=res, cap=cap, srf=w0 / (2 * np.pi))
# --- Debug plots --- #
# plt.semilogx(freq[:idx0], zc.imag[:idx0], label='Measured')
# plt.semilogx(freq[:idx0], w[:idx0] * ind, label='Estimated')
# plt.xlabel('Frequency (in Hz)')
# plt.ylabel('Value')
# plt.legend()
# plt.show()
return ans
[docs]def estimate_esd(freq: np.ndarray, yc: np.ndarray) -> Tuple[float, float]:
"""assume yc = (1/R) + jwC; returns C, R"""
fit = np.polyfit(2 * np.pi * freq, np.imag(yc), 1)
cap: float = fit[0]
res: float = 1 / np.mean(yc.real)
return cap, res
[docs]def compute_passives(meas_results: Mapping[str, Any], passive_type: str) -> Mapping[str, Any]:
freq = meas_results['freq']
y11 = meas_results['y11']
y12 = meas_results['y12']
y21 = meas_results['y21']
y22 = meas_results['y22']
# --- Verify yc = -y12 = -y21 is consistent --- #
if not np.isclose(y12, y21, rtol=1e-3).all():
plt.loglog(freq, np.abs(y12), label='y12')
plt.loglog(freq, np.abs(y21), 'g--', label='y21')
plt.xlabel('Frequency (in Hz)')
plt.ylabel('Value')
plt.legend()
plt.grid()
plt.show()
yc = - (y12 + y21) / 2
ypp = y11 + y12
ypm = y22 + y21
results = dict(
cpp=estimate_cap(freq, ypp),
cpm=estimate_cap(freq, ypm),
)
if passive_type == 'cap':
results['cc'] = estimate_cap(freq, yc)
results['r_series'] = np.mean(1 / yc).real
elif passive_type == 'res':
results['c_parallel'], results['res'] = estimate_esd(freq, yc)
warr = 2 * np.pi * freq
z_meas = 1 / (yc + (ypp * ypm) / (ypp + ypm))
if results['cpp'] == 0 or results['cpm'] == 0:
cp_est = 0
else:
cp_est = 1 / (1 / results['cpp'] + 1 / results['cpm'])
z_est = 1 / (1 / results['res'] + 1j * warr * (results['c_parallel'] + cp_est))
plt.semilogx(freq, np.abs(z_meas), label='Measured')
plt.semilogx(freq, np.abs(z_est), label='Estimated')
plt.xlabel('Frequency (in Hz)')
plt.ylabel('Value')
plt.legend()
plt.grid()
plt.show()
elif passive_type == 'esd':
results['cc'], results['res'] = estimate_esd(freq, yc)
elif passive_type == 'ind':
zc = 1 / yc
ind_values = estimate_ind(freq, zc)
results.update(ind_values)
# --- Debug plots --- #
warr = 2 * np.pi * freq
z_est = 1 / (1 / (ind_values['res'] + 1j * warr * ind_values['ind']) + 1j * warr * ind_values['cap'])
plt.semilogx(freq, zc.imag, label='Measured')
plt.semilogx(freq, z_est.imag, label='Estimated')
plt.xlabel('Frequency (in Hz)')
plt.ylabel('Value')
plt.legend()
plt.grid()
plt.show()
else:
raise ValueError(f'Unknown passive_type={passive_type}. Use "cap" or "res" or "esd" or "ind".')
return results