Source code for bag3_testbenches.measurement.digital.flop.array

# 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, Sequence, Tuple, Set, Mapping, Dict, Iterable

from pybag.core import get_cdba_name_bits

from bag3_liberty.data import parse_cdba_name

from ...data.tran import EdgeType
from .base import FlopTimingBase, FlopMeasMode, FlopInputMode


[docs]class FlopArrayTimingTB(FlopTimingBase): """This class performs transient simulation on an array of flops this testbench assumes the following: 1. all flops share the same clock, reset, and scan enable signal. 2. output of one flop goes into the scan_in of the next one. Notes ----- specification dictionary has the following entries in addition to those in FlopTimingBase: flop_params : Mapping[str, Any] Flop parameters, with the following entries: in_pin : str the input pin(s), in CDBA format. the first pin in the string corresponds to the first flop in the scan chain. out_pin : str the output pin(s), in CDBA format. clk_pin : str the clock pin name. se_pin : str Optional. The scan enable pin name. si_pin : str Optional. The first scan input pin. rst_pin : str Optional. The reset pin name. rst_active_high : bool Defaults to True. True if reset pin is active high. rst_to_high : bool Defaults to False. True if output is high during reset. rst_timing : bool True to characterize timing on reset pin. out_invert : bool Defaults to False. True if outputs are inverted from input. clk_rising : bool True if flop trigger on rising edge of clock. out_timing_pin : str Defaults to out_pin. The output pin(s) for which to add clock-to-q delay information, as a string in CDBA format. in_timing_pin: str Defaults to in_pin. The input pin(s) for which to add timing information, as a string in CDBA format. c_load_pin: str Defaults to out_timing_pin. The output pin(s) for which to add load capacitor. setup_offset : float Defaults to 0. Offset to add to setup time. hold_offset : float Defaults to 0. Offset to add to hold time. delay_offset : float Defaults to 0. Offset to add to clock-to-q delay. """ def __init__(self, *args: Any, **kwargs: Any) -> None: self._in_list: Sequence[str] = [] self._out_list: Sequence[str] = [] self._in_timing_set: Set[str] = set() self._out_timing_set: Set[str] = set() self._c_load_list: Sequence[str] = [] super().__init__(*args, **kwargs)
[docs] def commit(self) -> None: # NOTE: initialize in_list and out_list before running super's initialization, # so get_stimuli() behaves correctly. flop_params: Mapping[str, Any] = self.specs['flop_params'] in_pin: str = flop_params['in_pin'] out_pin: str = flop_params['out_pin'] si_pin: str = flop_params.get('si_pin', '') in_timing_pin: str = flop_params.get('in_timing_pin', in_pin) out_timing_pin: str = flop_params.get('out_timing_pin', out_pin) c_load_pin: str = flop_params.get('out_timing_pin', out_timing_pin) self._in_list = get_cdba_name_bits(in_pin) self._out_list = get_cdba_name_bits(out_pin) if len(self._in_list) != len(self._out_list): raise ValueError('different number of input and output pins') if in_timing_pin: self._in_timing_set = set(get_cdba_name_bits(in_timing_pin)) else: self._in_timing_set = set() if si_pin: self._in_timing_set.add(si_pin) if out_timing_pin: self._out_timing_set = set(get_cdba_name_bits(out_timing_pin)) else: self._out_timing_set = set() if c_load_pin: self._c_load_list = get_cdba_name_bits(c_load_pin) else: self._c_load_list = [] super().commit()
@property
[docs] def num_cycles(self) -> int: if self.meas_mode.is_reset: return 1 return 2
@property
[docs] def c_load_pins(self) -> Iterable[str]: return self._c_load_list
@classmethod
[docs] def get_default_flop_params(cls) -> Dict[str, Any]: return dict(se_pin='', si_pin='', rst_pin='', rst_active_high=True, rst_to_high=False, out_invert=False, clk_rising=True, setup_offset=0, hold_offset=0, delay_offset=0)
@classmethod
[docs] def get_meas_modes(cls, flop_params: Mapping[str, Any]) -> Sequence[FlopMeasMode]: se_pin: str = flop_params.get('se_pin', '') si_pin: str = flop_params.get('si_pin', '') rst_pin: str = flop_params.get('rst_pin', '') rst_timing: bool = flop_params.get('rst_timing', True) clk_rising: bool = flop_params.get('clk_rising', True) in_timing_pin: str = flop_params.get('in_timing_pin', flop_params['in_pin']) ans = [] if in_timing_pin: ans.append(FlopMeasMode(in_mode=FlopInputMode.IN, in_rising=True, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=True)) ans.append(FlopMeasMode(in_mode=FlopInputMode.IN, in_rising=True, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=False)) ans.append(FlopMeasMode(in_mode=FlopInputMode.IN, in_rising=False, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=True)) ans.append(FlopMeasMode(in_mode=FlopInputMode.IN, in_rising=False, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=False)) if se_pin and si_pin: ans.append(FlopMeasMode(in_mode=FlopInputMode.SE, in_rising=True, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=True)) ans.append(FlopMeasMode(in_mode=FlopInputMode.SE, in_rising=True, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=False)) ans.append(FlopMeasMode(in_mode=FlopInputMode.SE, in_rising=False, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=True)) ans.append(FlopMeasMode(in_mode=FlopInputMode.SE, in_rising=False, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=False)) ans.append(FlopMeasMode(in_mode=FlopInputMode.SI, in_rising=True, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=True)) ans.append(FlopMeasMode(in_mode=FlopInputMode.SI, in_rising=True, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=False)) ans.append(FlopMeasMode(in_mode=FlopInputMode.SI, in_rising=False, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=True)) ans.append(FlopMeasMode(in_mode=FlopInputMode.SI, in_rising=False, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=False)) if rst_pin and rst_timing: ans.append(FlopMeasMode(in_mode=FlopInputMode.RECOVERY, in_rising=True, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=True)) ans.append(FlopMeasMode(in_mode=FlopInputMode.REMOVAL, in_rising=True, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=True)) return ans
@classmethod
[docs] def get_output_meas_modes(cls, flop_params: Mapping[str, Any]) -> Sequence[FlopMeasMode]: clk_rising: bool = flop_params.get('clk_rising', True) ans = [FlopMeasMode(in_mode=FlopInputMode.IN, in_rising=True, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=True), FlopMeasMode(in_mode=FlopInputMode.IN, in_rising=False, setup_rising=clk_rising, hold_rising=clk_rising, meas_setup=True), ] return ans
@classmethod
[docs] def get_setup_hold_name(cls, pin: str) -> Tuple[str, str]: basename, bus_range = parse_cdba_name(pin) if bus_range is None: var_setup = f't_setup_{basename}_' var_hold = f't_hold_{basename}_' else: var_setup = f't_setup_{basename}_{bus_range[0]}' var_hold = f't_hold_{basename}_{bus_range[0]}' return var_setup, var_hold
@classmethod
[docs] def get_recovery_removal_name(cls, pin: str) -> Tuple[str, str]: basename, bus_range = parse_cdba_name(pin) if bus_range is None: v1 = f't_recovery_{basename}_' v2 = f't_removal_{basename}_' else: v1 = f't_recovery_{basename}_{bus_range[0]}' v2 = f't_removal_{basename}_{bus_range[0]}' return v1, v2
[docs] def get_stimuli(self) -> Tuple[Sequence[Mapping[str, Any]], Dict[str, int], Set[str], Sequence[str]]: mode = self.meas_mode flop_params = self.flop_params clk_pin: str = flop_params['clk_pin'] se_pin: str = flop_params['se_pin'] si_pin: str = flop_params['si_pin'] rst_pin: str = flop_params['rst_pin'] rst_active_high: bool = flop_params['rst_active_high'] rst_to_high: bool = flop_params['rst_to_high'] clk_rising: bool = flop_params['clk_rising'] if clk_rising != mode.is_pos_edge_clk: raise ValueError(f'Cannot perform measurement {mode.name}, clk phase is wrong.') pulses = [self.get_clk_pulse(clk_pin, clk_rising)] biases = {} var_list = [] if mode.is_input or mode.is_scan_in: pos = mode.input_rising if mode.is_scan_in: if not se_pin or not si_pin: raise ValueError('No scan pins defined.') stimuli_pins = [si_pin] for pin in self._in_list: biases[pin] = 0 biases[se_pin] = 1 else: stimuli_pins = self._in_list if se_pin and si_pin: biases[si_pin] = biases[se_pin] = 0 if rst_pin: pulses.append(self.get_rst_pulse(rst_pin, rst_active_high)) for pin in stimuli_pins: var_setup, var_hold = self.get_setup_hold_name(pin) var_list.append(var_setup) var_list.append(var_hold) pulses.append(self.get_input_pulse(pin, var_setup, var_hold, pos)) elif mode.is_reset: if not rst_pin: raise ValueError('No reset pin defined.') if se_pin and si_pin: biases[si_pin] = biases[se_pin] = 0 in_val = int(not rst_to_high) for pin in self._in_list: biases[pin] = in_val is_recovery = mode.is_recovery var_name = self.get_recovery_removal_name(rst_pin)[int(not is_recovery)] var_list.append(var_name) pulses.append(self.get_rst_pulse(rst_pin, rst_active_high, var_name=var_name, is_recovery=is_recovery)) elif mode.is_scan_en: if not se_pin or not si_pin: raise ValueError('No scan pins defined.') if rst_pin: pulses.append(self.get_rst_pulse(rst_pin, rst_active_high)) var_setup, var_hold = self.get_setup_hold_name(se_pin) clk_mid = 't_clk_delay+t_clk_per' var_list.append(var_setup) var_list.append(var_hold) if mode.is_scan_en and mode.input_rising: for idx, pin in enumerate(self._in_list): biases[pin] = (idx & 1) biases[si_pin] = 1 pulses.append(self.get_input_pulse(se_pin, var_setup, var_hold, True)) else: biases[si_pin] = 0 for pin in self._in_list: pulses.append(dict(pin=pin, tper='2*t_sim', tpw='t_sim', trf='t_clk_rf', td='t_clk_delay+t_clk_per/2', pos=True)) pulses.append(dict(pin=se_pin, tper=f't_clk_per/2+{var_hold}', tpw=f't_clk_per/2-{var_setup}', trf='t_rf', td=f'{clk_mid}-t_clk_per/2', pos=True)) else: raise ValueError(f'Unsupported flop measurement mode: {mode}') outputs = set(self._in_list) outputs.update(self._out_list) outputs.add(clk_pin) if se_pin: outputs.add(se_pin) if si_pin: outputs.add(si_pin) if rst_pin: outputs.add(rst_pin) return pulses, biases, outputs, var_list
[docs] def get_output_map(self, output_timing: bool ) -> Mapping[str, Tuple[Mapping[str, Any], Sequence[Tuple[EdgeType, Sequence[str]]]]]: mode = self.meas_mode flop_params = self.flop_params clk_pin: str = flop_params['clk_pin'] se_pin: str = flop_params['se_pin'] rst_pin: str = flop_params['rst_pin'] out_invert: bool = flop_params['out_invert'] rst_to_high: bool = flop_params['rst_to_high'] rst_active_high: bool = flop_params['rst_active_high'] setup_offset: float = flop_params['setup_offset'] hold_offset: float = flop_params['hold_offset'] delay_offset: float = flop_params['delay_offset'] meas_setup = mode.meas_setup sh_idx = int(not meas_setup) if output_timing: offset = delay_offset elif mode.is_recovery or meas_setup: offset = setup_offset else: offset = hold_offset if rst_pin: if rst_active_high: cond_rst_off = f'!{rst_pin}' else: cond_rst_off = rst_pin else: cond_rst_off = '' if se_pin and not output_timing: cond_se_off = f'!{se_pin}' else: cond_se_off = '' ans = {} if mode.is_input or mode.is_scan_in: if mode.is_input: in_out_iter = zip(self._in_list, self._out_list) cond_str = ' && '.join((val for val in [cond_se_off, cond_rst_off] if val)) else: si_pin: str = flop_params['si_pin'] if not si_pin or not se_pin: raise ValueError('No scan in pin defined.') in_out_iter = [(si_pin, self._out_list[0])] cond_str = se_pin if not rst_pin else f'{se_pin} && {cond_rst_off}' for in_pin, out_pin in in_out_iter: if ((output_timing and out_pin in self._out_timing_set) or (not output_timing and in_pin in self._in_timing_set)): var_name = self.get_setup_hold_name(in_pin)[sh_idx] out_diff_grp = self.get_diff_groups(out_pin) rise_idx = int(not (mode.input_rising ^ out_invert)) timing_info = self.get_timing_info(mode, [in_pin], clk_pin, cond_str, rst_active_high, offset=offset) edge_out_list = [(EdgeType.RISE, out_diff_grp[rise_idx]), (EdgeType.FALL, out_diff_grp[rise_idx ^ 1])] ans[var_name] = (timing_info, edge_out_list) elif mode.is_scan_en: if not se_pin: raise ValueError('No scan en pin defined.') cond_str = se_pin if not rst_pin else f'{se_pin} && {cond_rst_off}' rf_list = ([], []) for out_idx, out_pin in enumerate(self._out_list): out_diff_grp = self.get_diff_groups(out_pin) out_falling = mode.is_scan_en and mode.input_rising and (out_idx & 1) pos_rf_idx = int(out_invert ^ out_falling) rf_list[pos_rf_idx].extend(out_diff_grp[0]) rf_list[pos_rf_idx ^ 1].extend(out_diff_grp[1]) var_name = self.get_setup_hold_name(se_pin)[sh_idx] timing_info = self.get_timing_info(mode, [se_pin], clk_pin, cond_str, rst_active_high, offset=offset) edge_out_list = [(EdgeType.RISE, rf_list[0]), (EdgeType.FALL, rf_list[1])] ans[var_name] = (timing_info, edge_out_list) elif mode.is_reset: if not rst_pin: raise ValueError('No reset pin defined.') out_falling = not ((not rst_to_high) ^ out_invert) rf_list = ([], []) for out_pin in self._out_list: out_diff_grp = self.get_diff_groups(out_pin) pos_rf_idx = int(out_invert ^ out_falling) rf_list[pos_rf_idx].extend(out_diff_grp[0]) rf_list[pos_rf_idx ^ 1].extend(out_diff_grp[1]) var_re, var_rm = self.get_recovery_removal_name(rst_pin) var_name = var_re if mode.is_recovery else var_rm timing_info = self.get_timing_info(mode, [rst_pin], clk_pin, '', rst_active_high, offset=offset) edge_out_list = [(EdgeType.RISE, rf_list[0]), (EdgeType.FALL, rf_list[1])] ans[var_name] = (timing_info, edge_out_list) else: raise ValueError(f'Unsupported mode: {mode}') return ans