Source code for bag3_testbenches.measurement.digital.delay
# SPDX-License-Identifier: Apache-2.0
# Copyright 2019 Blue Cheetah Analog Design Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Any, Tuple, Optional, Mapping, Sequence
import pprint
from pathlib import Path
from matplotlib import pyplot as plt
import numpy as np
from scipy.stats import linregress
from bag.io.file import write_yaml
from bag.concurrent.util import GatherHelper
from bag.simulation.cache import DesignInstance, SimulationDB
from bag.simulation.measure import MeasurementManager
from ..tran.digital import DigitalTranTB
from .comb import CombLogicTimingMM
[docs]class RCDelayCharMM(MeasurementManager):
"""Characterize delay of a digital gate using pulse sources in series with a resistor.
Notes
-----
specification dictionary has the following entries:
in_pin : Union[str, Sequence[str]]
input pin(s)
out_pin : Union[str, Sequence[str]]
output pin(s)
out_invert : Union[bool, Sequence[bool]]
True if output is inverted from input. Corresponds to each input/output pair.
tbm_specs : Mapping[str, Any]
DigitalTranTB related specifications. The following simulation parameters are required:
t_rst :
reset duration.
t_rst_rf :
reset rise/fall time.
t_bit :
bit value duration.
t_rf :
input rise/fall time
r_src : float
nominal source resistance.
c_load : float
nominal load capacitance.
scale_min : float
lower bound scale factor for c_load/r_src.
scale_max : float
upper bound scale factor for c_load/r_src.
num_samples : int
number of data points to measure.
c_in : float
Defaults to 0. If nonzero, add this input capacitance.
wait_cycles : int
Defaults to 0. Number of cycles to wait toggle before finally measuring delay.
t_step_min : float
Defaults to 0.1e-12 (0.1 ps). rise/fall time of the step function approximation.
plot : bool
Defaults to False. True to plot fitted lines.
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
self._cin_specs: Mapping[str, Any] = {}
self._td_specs: Mapping[str, Any] = {}
super().__init__(*args, **kwargs)
[docs] def commit(self) -> None:
super().commit()
specs = self.specs
in_pin: str = specs['in_pin']
out_pin: str = specs['out_pin']
out_invert: bool = specs['out_invert']
tbm_specs: Mapping[str, Any] = specs['tbm_specs']
r_src: float = specs['r_src']
c_load: float = specs['c_load']
scale_min: float = specs['scale_min']
scale_max: float = specs['scale_max']
num_samples: int = specs['num_samples']
c_in: float = specs.get('c_in', 0)
wait_cycles: int = specs.get('wait_cycles', 0)
t_step_min: float = specs.get('t_step_min', 0.1e-12)
cin_tbm_specs = dict(**tbm_specs)
cin_tbm_specs['sim_params'] = sim_params = dict(**cin_tbm_specs['sim_params'])
sim_params['r_src'] = r_src
sim_params['c_load'] = c_load
sim_params['t_rf'] = t_step_min
sim_params['t_bit'] = f'10*(r_src+{r_src:.4g})*(c_load+{c_load:.4g})'
cin_tbm_specs['swp_info'] = [('r_src', dict(type='LOG', start=r_src * scale_min,
stop=r_src * scale_max, num=num_samples))]
if c_in:
sim_params['c_in'] = c_in
load_list = [dict(pin=in_pin, type='cap', value='c_in')]
else:
load_list = None
self._cin_specs = dict(
in_pin=in_pin,
out_pin=out_pin,
out_invert=False,
tbm_specs=cin_tbm_specs,
start_pin=DigitalTranTB.get_r_src_pin(in_pin),
stop_pin=in_pin,
out_rise=True,
out_fall=True,
wait_cycles=wait_cycles,
add_src_res=True,
load_list=load_list,
)
td_tbm_specs = cin_tbm_specs.copy()
td_tbm_specs['swp_info'] = [('c_load', dict(type='LOG', start=c_load * scale_min,
stop=c_load * scale_max, num=num_samples))]
self._td_specs = dict(
in_pin=in_pin,
out_pin=out_pin,
out_invert=out_invert,
tbm_specs=td_tbm_specs,
out_rise=True,
out_fall=True,
wait_cycles=wait_cycles,
add_src_res=True,
load_list=load_list,
)
[docs] async def async_measure_performance(self, name: str, sim_dir: Path, sim_db: SimulationDB,
dut: Optional[DesignInstance],
harnesses: Optional[Sequence[DesignInstance]] = None) -> Mapping[str, Any]:
specs = self.specs
in_pin: str = specs['in_pin']
out_pin: str = specs['out_pin']
c_in: float = specs.get('c_in', 0)
t_step_min: float = specs.get('t_step_min', 0.1e-12)
plot: bool = specs.get('plot', False)
cin_mm = sim_db.make_mm(CombLogicTimingMM, self._cin_specs)
td_mm = sim_db.make_mm(CombLogicTimingMM, self._td_specs)
gatherer = GatherHelper()
gatherer.append(sim_db.async_simulate_mm_obj(f'{name}_cin', sim_dir / 'cin', dut, cin_mm))
gatherer.append(sim_db.async_simulate_mm_obj(f'{name}_td', sim_dir / 'td', dut, td_mm))
cin_output, td_output = await gatherer.gather_err()
t_unit = 10 * t_step_min
sim_envs = cin_output.data['sim_envs']
r_src = cin_output.data['sim_params']['r_src']
cin_timing = cin_output.data['timing_data'][in_pin]
td_cin_rise = cin_timing['cell_rise']
td_cin_fall = cin_timing['cell_fall']
rin_rise, cin_rise = self.fit_rc_in(td_cin_rise, r_src, c_in, t_unit)
rin_fall, cin_fall = self.fit_rc_in(td_cin_fall, r_src, c_in, t_unit)
td_timing = td_output.data['timing_data'][out_pin]
c_load = td_output.data['sim_params']['c_load']
td_out_rise = td_timing['cell_rise']
td_out_fall = td_timing['cell_fall']
rout_rise, cout_rise = self.fit_rc_out(td_out_rise, c_load, t_unit)
rout_fall, cout_fall = self.fit_rc_out(td_out_fall, c_load, t_unit)
if plot:
if len(sim_envs) > 100:
raise ValueError('Can only plot with num. sim_envs < 100')
for idx in range(len(sim_envs)):
ri_r = rin_rise[idx]
ri_f = rin_fall[idx]
ci_r = cin_rise[idx]
ci_f = cin_fall[idx]
ro_r = rout_rise[idx]
ro_f = rout_fall[idx]
co_r = cout_rise[idx]
co_f = cout_fall[idx]
rs = r_src[idx, ...]
cl = c_load[idx, ...]
td_cin_r = td_cin_rise[idx, ...]
td_cin_f = td_cin_fall[idx, ...]
td_out_r = td_out_rise[idx, ...]
td_out_f = td_out_fall[idx, ...]
plt.figure(idx * 100 + 1)
plt.title(f'{sim_envs[idx]} c_in')
plt.plot(rs, td_cin_r, 'bo', label='td_rise')
plt.plot(rs, td_cin_f, 'ko', label='td_fall')
plt.plot(rs, np.log(2) * rs * (ci_r + c_in) + ri_r * ci_r, '-r',
label='td_rise_fit')
plt.plot(rs, np.log(2) * rs * (ci_f + c_in) + ri_f * ci_f, '-g',
label='td_fall_fit')
plt.legend()
plt.figure(idx * 100 + 2)
plt.title(f'{sim_envs[idx]} rc_out')
plt.plot(cl, td_out_r, 'bo', label='td_rise')
plt.plot(cl, td_out_f, 'ko', label='td_fall')
plt.plot(cl, ro_r * (co_r + cl), '-r', label='td_rise_fit')
plt.plot(cl, ro_f * (co_f + cl), '-g', label='td_fall_fit')
plt.legend()
plt.show()
ans = dict(
sim_envs=sim_envs,
r_in=(rin_fall, rin_rise),
c_in=(cin_fall, cin_rise),
c_out=(cout_fall, cout_rise),
r_out=(rout_fall, rout_rise),
)
self.log(f'Measurement {name} done, result:\n{pprint.pformat(ans)}')
write_yaml(sim_dir / f'{name}.yaml', ans)
return ans
@classmethod
[docs] def fit_rc_in(cls, td: np.ndarray, rin_linear: np.ndarray, c_in_const: float, t_unit: float
) -> Tuple[np.ndarray, np.ndarray]:
# Note: delay = Rs * (Ci + Cp) + Ri*Ci
num_corners = td.shape[0]
rvec = np.empty(num_corners)
cvec = np.empty(num_corners)
for idx in range(num_corners):
c0, t0, _, _, _ = linregress(np.log(2) * rin_linear[idx, :],
td[idx, :] / t_unit)
ci = c0 - (c_in_const / t_unit)
ri = t0 / ci
ci *= t_unit
rvec[idx] = ri
cvec[idx] = ci
return rvec, cvec
@classmethod
[docs] def fit_rc_out(cls, td: np.ndarray, cl: np.ndarray, t_unit: float
) -> Tuple[np.ndarray, np.ndarray]:
num_corners = td.shape[0]
rvec = np.empty(num_corners)
cvec = np.empty(num_corners)
for idx in range(num_corners):
r0, t0, _, _, _ = linregress(cl[idx, :] / t_unit, td[idx, :] / t_unit)
c0 = t0 * t_unit / r0
rvec[idx] = r0
cvec[idx] = c0
return rvec, cvec