Source code for bag3_digital.measurement.cap.delay_match

# 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 __future__ import annotations

from typing import Any, Tuple, Mapping, Optional, Union, Sequence, cast

import pprint

from bag.simulation.core import TestbenchManager
from bag.simulation.cache import SimulationDB, DesignInstance, SimResults, MeasureResult
from bag.simulation.measure import MeasurementManager, MeasurementManagerFSM, MeasInfo

from bag3_testbenches.measurement.data.tran import EdgeType
from bag3_testbenches.measurement.tran.digital import DigitalTranTB
from bag3_testbenches.measurement.digital.util import setup_digital_tran
from bag3_testbenches.measurement.digital.delay_match import DelayMatch

from ..util import get_digital_wrapper_params, get_in_buffer_pin_names


[docs]class CapDelayMatch(MeasurementManagerFSM): """Measures input capacitance by matching delay. Assumes that no parameters/corners are swept. Notes ----- specification dictionary has the following entries: in_pin : str input pin name. fake : bool Defaults to False. True to generate fake data. buf_params : Optional[Mapping[str, Any]] Optional. Input buffer parameters. buf_config : Optional[Mapping[str, Any]] Optional. Either buf_params or buf_config must be specified. search_params : Mapping[str, Any] interval search parameters, with the following entries: low : float lower bound. high : Optional[float] upper bound. If None, perform a unbounded binary search. step : float initial step size for unbounded binary search. tol : float tolerance of the binary search. Terminate the search when it is below this value. max_err : float Used only in unbounded binary search. If unbounded binary search exceeds this value, raise an error. overhead_factor : float ratio of simulation startup time to time it takes to simulate one sweep point. 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 pulse rise/fall time. load_list : Sequence[Mapping[str, Any]] Optional. List of loads. Each dictionary has the following entries: pin: str the pin to connect to. type : str the load device type. value : Union[float, str] the load parameter value. """ def __init__(self, *args: Any, **kwargs: Any) -> None: self._tbm_info: Optional[Tuple[DigitalTranTB, Mapping[str, Any]]] = None self._mm: Optional[DelayMatch] = None self._wrapper_params: Mapping[str, Any] = {} # TODO: make cap measurement more accurate by determining buf_params automatically # TODO: add option to automatically adjust load cap to determine input cap accurately super().__init__(*args, **kwargs) @property
[docs] def wrapper_params(self) -> Mapping[str, Any]: return self._wrapper_params
[docs] def initialize(self, sim_db: SimulationDB, dut: DesignInstance, harnesses: Optional[Sequence[DesignInstance]] = None) -> Tuple[bool, MeasInfo]: specs = self.specs in_pin: str = specs['in_pin'] search_params = specs['search_params'] buf_params: Optional[Mapping[str, Any]] = specs.get('buf_params', None) buf_config: Optional[Mapping[str, Any]] = specs.get('buf_config', None) fake: bool = specs.get('fake', False) load_list: Sequence[Mapping[str, Any]] = specs.get('load_list', []) if fake: return True, MeasInfo('done', dict(cap_rise=1.0e-12, cap_fall=1.0e-12, tr_ref=50.0e-12, tf_ref=50.0e-12, tr_adj=50.0e-12, tf_adj=50.0e-12)) if buf_params is None and buf_config is None: raise ValueError('one of buf_params or buf_config must be specified.') if buf_params is None: lch: int = buf_config['lch'] w_p: int = buf_config['w_p'] w_n: int = buf_config['w_n'] th_p: str = buf_config['th_p'] th_n: str = buf_config['th_n'] cinv_unit: float = buf_config['cinv_unit'] cin_guess: float = buf_config['cin_guess'] fanout_in: float = buf_config['fanout_in'] fanout_buf: float = buf_config.get('fanout_buf', 4) seg1 = int(round(max(cin_guess / fanout_in / cinv_unit, 1.0))) seg0 = int(round(max(seg1 / fanout_buf, 1.0))) buf_params = dict( export_pins=True, inv_params=[ dict(lch=lch, w_p=w_p, w_n=w_n, th_p=th_p, th_n=th_n, seg=seg0), dict(lch=lch, w_p=w_p, w_n=w_n, th_p=th_p, th_n=th_n, seg=seg1), ], ) specs['buf_params'] = buf_params self.log(f'buf_params:\n{pprint.pformat(buf_params, width=100)}') search_params = dict(**search_params) search_params['guess'] = (cin_guess * 0.8, cin_guess * 1.2) # create testbench for measuring reference delay pulse_list = [dict(pin=in_pin, tper='2*t_bit', tpw='t_bit', trf='t_rf', td='t_bit', pos=True)] self._wrapper_params = get_digital_wrapper_params(specs, dut, [in_pin]) tbm_specs, tb_params = setup_digital_tran(specs, dut, wrapper_params=self._wrapper_params, pulse_list=pulse_list, load_list=load_list) buf_mid, buf_out = get_in_buffer_pin_names(in_pin) tbm_specs['save_outputs'] = [buf_mid, buf_out] # remove input pin from reset list reset_list: Sequence[Tuple[str, bool]] = tbm_specs.get('reset_list', []) new_reset_list = [ele for ele in reset_list if ele[0] != in_pin] tbm_specs['reset_list'] = new_reset_list tbm = cast(DigitalTranTB, sim_db.make_tbm(DigitalTranTB, tbm_specs)) if tbm.swp_info: self.error('Parameter sweep is not supported.') if tbm.num_sim_envs != 1: self.error('Corner sweep is not supported.') tbm.sim_params['t_sim'] = f'{tbm.t_rst_end_expr}+3*t_bit' self._tbm_info = tbm, tb_params # create DelayMatch mm_tbm_specs = {k: v for k, v in tbm.specs.items() if k not in {'pwr_domain', 'sup_values', 'dut_pins', 'pin_values', 'pulse_load', 'reset_list', 'load_list', 'diff_list'}} gnd_name, pwr_name = DigitalTranTB.get_pin_supplies(in_pin, tbm_specs['pwr_domain']) sup_values: Mapping[str, Union[float, Mapping[str, float]]] = tbm_specs['sup_values'] gnd_val = sup_values[gnd_name] pwr_val = sup_values[pwr_name] pwr_tup = ('VSS', 'VDD') mm_tbm_specs['pwr_domain'] = {} mm_tbm_specs['sup_values'] = dict(VSS=gnd_val, VDD=pwr_val) mm_tbm_specs['pin_values'] = {} thres_lo: float = mm_tbm_specs['thres_lo'] thres_hi: float = mm_tbm_specs['thres_hi'] t_start_expr = f't_rst+t_bit+(t_rst_rf-t_rf/2)/{thres_hi - thres_lo:.2f}' mm_specs = dict( adj_name='c_load', adj_sign=True, adj_params=dict(in_name='mid', out_name='out', t_start=t_start_expr), ref_delay=0, use_dut=False, search_params=search_params, tbm_specs=mm_tbm_specs, wrapper_params=dict( lib='bag3_digital', cell='inv_chain', params=buf_params, pins=['in', 'out', 'mid', 'VDD', 'VSS'], pwr_domain={'in': pwr_tup, 'out': pwr_tup, 'mid': pwr_tup}, ), pulse_list=[dict(pin='in', tper='2*t_bit', tpw='t_bit', trf='t_rf', td='t_bit', pos=True)], load_list=[dict(pin='out', type='cap', value='c_load')], ) mm_specs.update(search_params) self._mm = sim_db.make_mm(DelayMatch, mm_specs) return False, MeasInfo('init', {})
[docs] def get_sim_info(self, sim_db: SimulationDB, dut: DesignInstance, cur_info: MeasInfo, harnesses: Optional[Sequence[DesignInstance]] = None ) -> Tuple[Union[Tuple[TestbenchManager, Mapping[str, Any]], MeasurementManager], bool]: state = cur_info.state if state == 'init': return self._tbm_info, True elif state == 'cap_rise': mm_specs = self._mm.specs adj_params = mm_specs['adj_params'] adj_params['out_edge'] = EdgeType.RISE adj_params['in_edge'] = EdgeType.FALL mm_specs['ref_delay'] = cur_info.prev_results['tr_ref'] self._mm.commit() return self._mm, False elif state == 'cap_fall': cap_rise = cur_info.prev_results['cap_rise'] mm_specs = self._mm.specs adj_params = mm_specs['adj_params'] adj_params['out_edge'] = EdgeType.FALL adj_params['in_edge'] = EdgeType.RISE mm_specs['ref_delay'] = cur_info.prev_results['tf_ref'] search_params = mm_specs['search_params'] search_params['guess'] = (cap_rise * 0.9, cap_rise * 1.1) self._mm.commit() return self._mm, False else: raise ValueError(f'Unknown state: {state}')
[docs] def process_output(self, cur_info: MeasInfo, sim_results: Union[SimResults, MeasureResult] ) -> Tuple[bool, MeasInfo]: state = cur_info.state if state == 'init': tbm = self._tbm_info[0] in_pin: str = self.specs['in_pin'] sim_data = sim_results.data t0 = tbm.get_t_rst_end(sim_data) buf_mid, buf_out = get_in_buffer_pin_names(in_pin) tr = tbm.calc_delay(sim_data, buf_mid, buf_out, EdgeType.FALL, EdgeType.RISE, t_start=t0) tf = tbm.calc_delay(sim_data, buf_mid, buf_out, EdgeType.RISE, EdgeType.FALL, t_start=t0) return False, MeasInfo('cap_rise', dict(tr_ref=tr.item(), tf_ref=tf.item())) elif state == 'cap_rise': data = sim_results.data['c_load'] new_result = cur_info.prev_results.copy() new_result['cap_rise'] = data['value'] new_result['tr_adj'] = data['td_adj'] return False, MeasInfo('cap_fall', new_result) elif state == 'cap_fall': data = sim_results.data['c_load'] new_result = cur_info.prev_results.copy() new_result['cap_fall'] = data['value'] new_result['tf_adj'] = data['td_adj'] return True, MeasInfo('done', new_result) else: raise ValueError(f'Unknown state: {state}')