from __future__ import annotations
from typing import Any, Mapping, Optional, Union, Sequence
from pathlib import Path
from shutil import copy
import matplotlib.pyplot as plt
import numpy as np
from bag.simulation.cache import SimulationDB, DesignInstance, SimResults
from bag.simulation.measure import MeasurementManager
from bag.simulation.data import SimData
from bag.concurrent.util import GatherHelper
from bag.math import float_to_si_string
from bag3_testbenches.measurement.ac.base import ACTB
from bag3_testbenches.measurement.tran.digital import DigitalTranTB
from bag3_testbenches.measurement.tran.eye import EyeAnalysis, EyeResults
from bag3_testbenches.measurement.digital.util import setup_digital_tran
[docs]class DrvShuntPeakACMeas(MeasurementManager):
[docs] async def async_meas_pvt_case(self, name: str, sim_dir: Path, sim_db: SimulationDB, dut: Optional[DesignInstance],
harnesses: Optional[Sequence[DesignInstance]], pvt: str,
sweep_case: Mapping[str, Any]) -> SimData:
# outputs to be saved
save_outputs = ['i_outp', 'i_outm', 'v_inp', 'v_inm', 'v_tail_g', 'v_tail', 'ind_p', 'ind_m']
# create loads
load_list = [dict(pin='i_outp', type='cap', value='c_load'),
dict(pin='i_outp', nin='VDD', type='res', value='r_term'),
dict(pin='i_outm', type='cap', value='c_load'),
dict(pin='i_outm', nin='VDD', type='res', value='r_term')]
# inductors
ind_specs: Optional[Mapping[str, Any]] = self.specs.get('ind_specs')
if ind_specs:
ideal: bool = ind_specs['ideal']
if ideal:
load_list.extend([dict(pin='ind_p', nin='VDD', type='ind', value='l_shunt'),
dict(pin='ind_m', nin='VDD', type='ind', value='l_shunt')])
else:
sp_file_specs: Mapping[str, Any] = ind_specs['sp_file_specs']
sim_dir.mkdir(parents=True, exist_ok=True)
for key, _specs in sp_file_specs.items():
file_name: Path = Path(_specs['file_name'])
_num = file_name.suffix[2:-1]
ind_sp = f'{key}.s{_num}p'
copy(Path(file_name), sim_dir / ind_sp)
conns: Sequence[Mapping[str, str]] = _specs['conns']
for _idx, _conns in enumerate(conns):
load_list.append(dict(conns=_conns, type=f'n{_num}port', value=ind_sp,
name=f'NPORT_{key}_{_idx}'))
# create sources
load_list.extend([dict(pin='v_tail_g', type='vdc', value='v_tail_g'),
dict(pin='v_inp', type='vdc', value={'vdc': 'v_incm', 'acm': 0.5}),
dict(pin='v_inm', type='vdc', value={'vdc': 'v_incm', 'acm': -0.5})])
# setup harness
if harnesses:
harnesses_list: Sequence[Mapping[str, Any]] = self.specs['harnesses_list']
else:
harnesses_list = []
tb_params = dict(
load_list=load_list,
harnesses_list=harnesses_list,
sim_envs=[pvt],
ac_options={'oppoint': 'logfile'},
save_outputs=save_outputs,
)
tbm_specs, tb_params = setup_digital_tran(self.specs, dut, **tb_params)
tbm = self.make_tbm(ACTB, tbm_specs)
for key, val in sweep_case.items():
tbm.sim_params[key] = val
sim_results = await sim_db.async_simulate_tbm_obj(f'{name}_{pvt}_{get_label(sweep_case)}', sim_dir, dut, tbm,
tb_params, harnesses=harnesses)
return sim_results.data
@staticmethod
[docs] def plot_results(results: Mapping[str, Any]) -> None:
fig, ax_list = plt.subplots(1, len(results.keys()))
if not isinstance(ax_list, np.ndarray):
ax_list = np.array([ax_list])
aidx = 0
for sim_env, sweeps in results.items():
ax = ax_list[aidx]
for swidx, ans in results[sim_env].items():
ac_data = ans['data']
freq = ac_data['freq']
out_d = ac_data['i_outp'][0] - ac_data['i_outm'][0]
sweep_case: Mapping[str, Any] = ans['sweep_case']
ax.semilogx(freq, 20 * np.log10(np.abs(out_d)), label=get_label(sweep_case))
ax.legend()
ax.grid()
ax.set_xlabel('Frequency (Hz)')
ax.set_ylabel('AC gain (dB)')
ax.set_title(sim_env)
aidx += 1
plt.tight_layout()
plt.show()
[docs]def get_label(sweep_case: Optional[Mapping[str, Any]]) -> str:
if sweep_case is None:
return ''
_label_list = [f'{key}_{float_to_si_string(val)}' for key, val in sweep_case.items()]
return '_'.join(_label_list)
[docs]class DrvShuntPeakTranMeas(MeasurementManager):
[docs] async def async_meas_pvt_case(self, name: str, sim_dir: Path, sim_db: SimulationDB, dut: Optional[DesignInstance],
harnesses: Optional[Sequence[DesignInstance]], pvt: str, v_incm: float,
v_tail_g: float) -> Mapping[str, Any]:
# outputs to be saved
save_outputs = ['i_outp', 'i_outm', 'v_inp', 'v_inm', 'v_tail_g', 'v_tail', 'ind_p', 'ind_m', 'VDC_VDD:p']
# create loads
load_list = [dict(pin='i_outp', type='cap', value='c_load'),
dict(pin='i_outp', nin='VDD', type='res', value='r_term'),
dict(pin='i_outm', type='cap', value='c_load'),
dict(pin='i_outm', nin='VDD', type='res', value='r_term')]
# inductors
ind_specs: Optional[Mapping[str, Any]] = self.specs.get('ind_specs')
if ind_specs:
ideal: bool = ind_specs['ideal']
if ideal:
load_list.extend([dict(pin='ind_p', nin='VDD', type='ind', value='l_shunt'),
dict(pin='ind_m', nin='VDD', type='ind', value='l_shunt')])
else:
sp_file_specs: Mapping[str, Any] = ind_specs['sp_file_specs']
sim_dir.mkdir(parents=True, exist_ok=True)
for key, _specs in sp_file_specs.items():
file_name: Path = Path(_specs['file_name'])
_num = file_name.suffix[2:-1]
ind_sp = f'{key}.s{_num}p'
copy(Path(file_name), sim_dir / ind_sp)
conns: Sequence[Mapping[str, str]] = _specs['conns']
for _idx, _conns in enumerate(conns):
load_list.append(dict(conns=_conns, type=f'n{_num}port', value=ind_sp,
name=f'NPORT_{key}_{_idx}'))
# create sources
load_list.extend([dict(pin='v_tail_g', type='vdc', value='v_tail_g'),
dict(conns={'vout': 'prbs_data'}, lib='ahdlLib', type='rand_bit_stream',
value={'tperiod': 't_per', 'vlogic_high': 'v_hi', 'vlogic_low': 'v_lo',
'tdel': 't_delay', 'trise': 't_rf', 'tfall': 't_rf', 'seed': 101}),
dict(conns={'d': 'prbs_data', 'p': 'v_inp', 'n': 'v_inm', 'c': 'v_incm'}, type='ideal_balun',
value={}),
dict(pin='v_incm', type='vdc', value='v_incm')])
# setup harness
if harnesses:
harnesses_list: Sequence[Mapping[str, Any]] = self.specs['harnesses_list']
else:
harnesses_list = []
tb_params = dict(
load_list=load_list,
harnesses_list=harnesses_list,
sim_envs=[pvt],
tran_options={'errpreset': 'conservative', 'noisefmax': 'fmax_noise'},
save_outputs=save_outputs,
)
tbm_specs, tb_params = setup_digital_tran(self.specs, dut, **tb_params)
tbm = self.make_tbm(DigitalTranTB, tbm_specs)
tbm.sim_params['v_incm'] = v_incm
tbm.sim_params['v_tail_g'] = v_tail_g
sim_results = await sim_db.async_simulate_tbm_obj(name, sim_dir, dut, tbm, tb_params, harnesses=harnesses)
return self.process_results(sim_results)
@staticmethod
[docs] def process_results(sim_results: SimResults) -> Mapping[str, Any]:
sim_data = sim_results.data
time = sim_data['time']
# compute average current consumption
i_vdd = - sim_data['VDC_VDD:p'][0]
i_avg = np.trapz(i_vdd, time) / time[-1]
# analyze eye
out_d = sim_data['i_outp'][0] - sim_data['i_outm'][0]
tbm = sim_results.tbm
t_per = tbm.sim_params['t_per']
t_delay = tbm.sim_params['t_delay']
eye_ana = EyeAnalysis(t_per, t_delay)
return dict(
eye=eye_ana.analyze_eye(time, out_d),
i_avg=i_avg,
)
@staticmethod
[docs] def plot_results(results: Mapping[str, Any]) -> None:
sim_envs: Sequence[str] = results['sim_envs']
v_incm_swp: np.ndarray = results['v_incm']
v_tail_g_swp: np.ndarray = results['v_tail_g']
fig, ax_list = plt.subplots(len(sim_envs), len(v_incm_swp) * len(v_tail_g_swp))
if not isinstance(ax_list, np.ndarray):
ax_list = np.array([ax_list]).reshape((1, 1))
if len(v_incm_swp) * len(v_tail_g_swp) == 1:
ax_list = ax_list.reshape(len(sim_envs), 1)
for jdx, sim_env in enumerate(sim_envs):
for kdx, v_incm in enumerate(v_incm_swp):
for ldx, v_tail_g in enumerate(v_tail_g_swp):
eidx = kdx * len(v_tail_g_swp) + ldx
ax = ax_list[jdx, eidx]
eye_results: EyeResults = results['eye'][jdx][eidx]
eye_results.plot(ax)
ax.set_title(f'{sim_env}; v_incm={v_incm} V; v_tail_g={v_tail_g} V')
plt.tight_layout()
plt.show()