# SPDX-License-Identifier: BSD-3-Clause AND Apache-2.0
# Copyright 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.
# 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.
"""This module defines functions useful for digital verification/postprocessing.
"""
from typing import Optional, List, Tuple
import numpy as np
from .core import Waveform
[docs]def de_bruijn(n, symbols=None):
# type: (int, Optional[List[float]]) -> List[float]
"""Returns a De Bruijn sequence with subsequence of length n.
a De Bruijn sequence with subsequence of length n is a sequence such that
all possible subsequences of length n appear exactly once somewhere in the
sequence. This method is useful for simulating the worst case eye diagram
given finite impulse response.
Parameters
----------
n : int
length of the subsequence.
symbols : Optional[List[float]] or None
the list of symbols. If None, defaults to [0.0, 1.0].
Returns
-------
seq : List[float]
the de bruijn sequence.
"""
symbols = symbols or [0.0, 1.0]
k = len(symbols)
a = [0] * (k * n)
sequence = []
def db(t, p):
if t > n:
if n % p == 0:
sequence.extend(a[1:p + 1])
else:
a[t] = a[t - p]
db(t + 1, p)
for j in range(a[t - p] + 1, k):
a[t] = j
db(t + 1, t)
db(1, 1)
return [symbols[i] for i in sequence]
[docs]def dig_to_pwl(values, tper, trf, td=0):
# type: (List[float], float, float, float) -> Tuple[List[float], List[float]]
"""Convert a list of digital bits to PWL waveform.
This function supports negative delay. However, time/value pairs for negative data
are truncated.
Parameters
----------
values : List[float]
list of values for each bit.
tper : float
the period in seconds.
trf : float
the rise/fall time in seconds.
td : float
the delay
Returns
-------
tvec : List[float]
the time vector.
yvec : List[float]
the value vector.
"""
y0 = values[0]
tcur, ycur = td, y0
tvec, yvec = [], []
for v in values:
if v != ycur:
if tcur >= 0:
tvec.append(tcur)
yvec.append(ycur)
elif tcur < 0 < tcur + trf:
# make sure time starts at 0
tvec.append(0)
yvec.append(ycur - (v - ycur) / trf * tcur)
ycur = v
if tcur + trf >= 0:
tvec.append(tcur + trf)
yvec.append(ycur)
elif tcur + trf < 0 < tcur + tper:
# make sure time starts at 0
tvec.append(0)
yvec.append(ycur)
tcur += tper
else:
if tcur <= 0 < tcur + tper:
# make sure time starts at 0
tvec.append(0)
yvec.append(ycur)
tcur += tper
if not tvec:
# only here if input is constant
tvec = [0, tper]
yvec = [y0, y0]
elif tvec[0] > 0:
# make time start at 0
tvec.insert(0, 0)
yvec.insert(0, y0)
return tvec, yvec
[docs]def get_crossing_index(yvec, threshold, n=0, rising=True):
# type: (np.array, float, int, bool) -> int
"""Returns the first index that the given numpy array crosses the given threshold.
Parameters
----------
yvec : np.array
the numpy array.
threshold : float
the crossing threshold.
n : int
returns the nth edge index, with n=0 being the first index.
rising : bool
True to return rising edge index. False to return falling edge index.
Returns
-------
idx : int
the crossing edge index.
"""
bool_vec = yvec >= threshold
qvec = bool_vec.astype(int)
dvec = np.diff(qvec)
dvec = np.maximum(dvec, 0) if rising else np.minimum(dvec, 0)
idx_list = dvec.nonzero()[0]
return idx_list[n]
[docs]def get_flop_timing(tvec, d, q, clk, ttol, data_thres=0.5,
clk_thres=0.5, tstart=0.0, clk_edge='rising', tag=None, invert=False):
"""Calculate flop timing parameters given the associated waveforms.
This function performs the following steps:
1. find all valid clock edges. Compute period of the clock (clock waveform
must be periodic).
2. For each valid clock edge:
A. Check if the input changes in the previous cycle. If so, compute tsetup.
Otherwise, tsetup = tperiod.
B. Check if input changes in the current cycle. If so, compute thold.
Otherwise, thold = tperiod.
C. Check that output transition at most once and that output = input.
Otherwise, record an error.
D. record the output data polarity.
3. For each output data polarity, compute the minimum tsetup and thold and any
errors. Return summary as a dictionary.
The output is a dictionary with keys 'setup', 'hold', 'delay', and 'errors'.
the setup/hold/delay entries contains 2-element tuples describing the worst
setup/hold/delay time. The first element is the setup/hold/delay time, and
the second element is the clock edge time at which it occurs. The errors field
stores all clock edge times at which an error occurs.
Parameters
----------
tvec : np.ndarray
the time data.
d : np.ndarray
the input data.
q : np.ndarray
the output data.
clk : np.ndarray
the clock data.
ttol : float
time resolution.
data_thres : float
the data threshold.
clk_thres : float
the clock threshold.
tstart : float
ignore data points before tstart.
clk_edge : str
the clock edge type. Valid values are "rising", "falling", or "both".
tag : obj
an identifier tag to append to results.
invert : bool
if True, the flop output is inverted from the data.
Returns
-------
data : dict[str, any]
A dictionary describing the worst setup/hold/delay and errors, if any.
"""
d_wv = Waveform(tvec, d, ttol)
clk_wv = Waveform(tvec, clk, ttol)
q_wv = Waveform(tvec, q, ttol)
tend = tvec[-1]
# get all clock sampling times and clock period
samp_times = clk_wv.get_all_crossings(clk_thres, start=tstart, edge=clk_edge)
tper = (samp_times[-1] - samp_times[0]) / (len(samp_times) - 1)
# ignore last clock cycle if it's not a full cycle.
if samp_times[-1] + tper > tend:
samp_times = samp_times[:-1]
# compute setup/hold/error for each clock period
data = {'setup': (tper, -1), 'hold': (tper, -1), 'delay': (0.0, -1), 'errors': []}
for t in samp_times:
d_prev = d_wv.get_all_crossings(data_thres, start=t - tper, stop=t, edge='both')
d_cur = d_wv.get_all_crossings(data_thres, start=t, stop=t + tper, edge='both')
q_cur = q_wv.get_all_crossings(data_thres, start=t, stop=t + tper, edge='both')
d_val = d_wv(t) > data_thres
q_val = q_wv(t + tper) > data_thres
# calculate setup/hold/delay
tsetup = t - d_prev[-1] if d_prev else tper
thold = d_cur[0] - t if d_cur else tper
tdelay = q_cur[0] - t if q_cur else 0.0
# check if flop has error
error = (invert != (q_val != d_val)) or (len(q_cur) > 1)
# record results
if tsetup < data['setup'][0]:
data['setup'] = (tsetup, t)
if thold < data['hold'][0]:
data['hold'] = (thold, t)
if tdelay > data['delay'][0]:
data['delay'] = (tdelay, t)
if error:
data['errors'].append(t)
if tag is not None:
data['setup'] += (tag, )
data['hold'] += (tag, )
data['delay'] += (tag, )
data['errors'] = [(t, tag) for t in data['errors']]
return data