4 changed files with 552 additions and 1125 deletions
			
			
		@ -0,0 +1,365 @@
				@@ -0,0 +1,365 @@
					 | 
				
			||||
"""High-throughput combinational logic timing simulators. | 
				
			||||
 | 
				
			||||
These simulators work similarly to :py:class:`~kyupy.logic_sim.LogicSim`. | 
				
			||||
They propagate values through the combinational circuit from (pseudo) primary inputs to (pseudo) primary outputs. | 
				
			||||
Instead of propagating logic values, these simulators propagate signal histories (waveforms). | 
				
			||||
They are designed to run many simulations in parallel and while their latencies are quite high, they can achieve | 
				
			||||
high throughput. | 
				
			||||
 | 
				
			||||
The simulators are not event-based and are not capable of simulating sequential circuits directly. | 
				
			||||
""" | 
				
			||||
 | 
				
			||||
import math | 
				
			||||
from bisect import bisect, insort_left | 
				
			||||
 | 
				
			||||
import numpy as np | 
				
			||||
 | 
				
			||||
from . import numba, cuda, hr_bytes | 
				
			||||
from .sim import SimOps | 
				
			||||
 | 
				
			||||
 | 
				
			||||
TMAX = np.float32(2 ** 127) | 
				
			||||
"""A large 32-bit floating point value used to mark the end of a waveform.""" | 
				
			||||
TMAX_OVL = np.float32(1.1 * 2 ** 127) | 
				
			||||
"""A large 32-bit floating point value used to mark the end of a waveform that | 
				
			||||
may be incomplete due to an overflow.""" | 
				
			||||
TMIN = np.float32(-2 ** 127) | 
				
			||||
"""A large negative 32-bit floating point value used at the beginning of waveforms that start with logic-1.""" | 
				
			||||
 | 
				
			||||
 | 
				
			||||
class WaveSim(SimOps): | 
				
			||||
    """A waveform-based combinational logic timing simulator running on CPU. | 
				
			||||
 | 
				
			||||
    :param circuit: The circuit to simulate. | 
				
			||||
    :param timing: The timing annotation of the circuit (see :py:func:`kyupy.sdf.DelayFile.annotation` for details) | 
				
			||||
    :param sims: The number of parallel simulations. | 
				
			||||
    :param c_caps: The number of floats available in each waveform. Values must be positive and a multiple of 4. | 
				
			||||
        Waveforms encode the signal switching history by storing transition times. | 
				
			||||
        The waveform capacity roughly corresponds to the number of transitions | 
				
			||||
        that can be stored. A capacity of ``n`` can store at least ``n-2`` transitions. If more transitions are | 
				
			||||
        generated during simulation, the latest glitch is removed (freeing up two transition times) and an overflow | 
				
			||||
        flag is set. If an integer is given, all waveforms are set to that same capacity. With an array of length | 
				
			||||
        ``len(circuit.lines)`` the capacity is set for each intermediate waveform individually. | 
				
			||||
    :param strip_forks: If enabled, the simulator will not evaluate fork nodes explicitly. This saves simulation time | 
				
			||||
        by reducing the number of nodes to simulate, but (interconnect) delay annotations of lines read by fork nodes | 
				
			||||
        are ignored. | 
				
			||||
    :param keep_waveforms: If disabled, memory of intermediate signal waveforms will be re-used. This greatly reduces | 
				
			||||
        memory footprint, but intermediate signal waveforms become unaccessible after a propagation. | 
				
			||||
    """ | 
				
			||||
    def __init__(self, circuit, timing, sims=8, c_caps=16, c_reuse=False, strip_forks=False): | 
				
			||||
        assert c_caps > 0 and c_caps % 4 == 0 | 
				
			||||
        super().__init__(circuit, c_caps=c_caps//4, c_reuse=c_reuse, strip_forks=strip_forks) | 
				
			||||
        self.sims = sims | 
				
			||||
         | 
				
			||||
        self.c_len *= 4 | 
				
			||||
        self.vat[...,0:2] *= 4 | 
				
			||||
 | 
				
			||||
        self.timing = np.zeros((self.c_len, 2, 2)) | 
				
			||||
        self.timing[:len(timing)] = timing | 
				
			||||
 | 
				
			||||
        self.c = np.zeros((self.c_len, sims), dtype=np.float32) + TMAX | 
				
			||||
        self.s = np.zeros((len(self.s_nodes), sims, 11), dtype=np.float32) | 
				
			||||
        """Information about the logic values and transitions around the sequential elements (flip-flops) and ports. | 
				
			||||
 | 
				
			||||
        The first 3 values are read by ``s_to_c()``. | 
				
			||||
        The remaining values are written by ``c_to_s()``. | 
				
			||||
 | 
				
			||||
        The elements are as follows: | 
				
			||||
        * ``s[..., 0]`` (P)PI initial value | 
				
			||||
        * ``s[..., 1]`` (P)PI transition time | 
				
			||||
        * ``s[..., 2]`` (P)PI final value | 
				
			||||
        * ``s[..., 3]`` (P)PO initial value | 
				
			||||
        * ``s[..., 4]`` (P)PO earliest arrival time (EAT): The time at which the output transitioned from its initial value. | 
				
			||||
        * ``s[..., 5]`` (P)PO latest stabilization time (LST): The time at which the output settled to its final value. | 
				
			||||
        * ``s[..., 6]`` (P)PO final value | 
				
			||||
        * ``s[..., 7]`` (P)PO capture value: probability of capturing a 1 at a given capture time | 
				
			||||
        * ``s[..., 8]`` (P)PO sampled capture value: decided by random sampling according to a given seed. | 
				
			||||
        * ``s[..., 9]`` (P)PO sampled capture slack: (capture time - LST) - decided by random sampling according to a given seed. | 
				
			||||
        * ``s[..., 10]`` Overflow indicator: If non-zero, some signals in the input cone of this output had more | 
				
			||||
          transitions than specified in ``wavecaps``. Some transitions have been discarded, the | 
				
			||||
          final values in the waveforms are still valid. | 
				
			||||
        """ | 
				
			||||
                      | 
				
			||||
        self.params = np.zeros((sims, 4), dtype=np.float32) | 
				
			||||
        self.params[...,0] = 1.0 | 
				
			||||
 | 
				
			||||
        m1 = np.array([2 ** x for x in range(7, -1, -1)], dtype=np.uint8) | 
				
			||||
        m0 = ~m1 | 
				
			||||
        self.mask = np.rollaxis(np.vstack((m0, m1)), 1) | 
				
			||||
 | 
				
			||||
        self.overflows = 0 | 
				
			||||
        self.lst_eat_valid = False | 
				
			||||
 | 
				
			||||
        self.pi_s_locs = np.flatnonzero(self.vat[self.ppi_offset+np.arange(len(self.circuit.io_nodes)), 0] >= 0) | 
				
			||||
        self.po_s_locs = np.flatnonzero(self.vat[self.ppo_offset+np.arange(len(self.circuit.io_nodes)), 0] >= 0) | 
				
			||||
        self.ppio_s_locs = np.arange(len(self.circuit.io_nodes), len(self.s_nodes)) | 
				
			||||
 | 
				
			||||
        self.pippi_s_locs = np.concatenate([self.pi_s_locs, self.ppio_s_locs]) | 
				
			||||
        self.poppo_s_locs = np.concatenate([self.po_s_locs, self.ppio_s_locs]) | 
				
			||||
 | 
				
			||||
        self.pi_c_locs = self.vat[self.ppi_offset+self.pi_s_locs, 0] | 
				
			||||
        self.po_c_locs = self.vat[self.ppo_offset+self.po_s_locs, 0] | 
				
			||||
        self.ppi_c_locs = self.vat[self.ppi_offset+self.ppio_s_locs, 0] | 
				
			||||
        self.ppo_c_locs = self.vat[self.ppo_offset+self.ppio_s_locs, 0] | 
				
			||||
 | 
				
			||||
        self.pippi_c_locs = np.concatenate([self.pi_c_locs, self.ppi_c_locs]) | 
				
			||||
        self.poppo_c_locs = np.concatenate([self.po_c_locs, self.ppo_c_locs]) | 
				
			||||
 | 
				
			||||
        self.wave_capture = numba.njit(WaveSim.wave_capture) | 
				
			||||
 | 
				
			||||
    def __repr__(self): | 
				
			||||
        total_mem = self.c.nbytes + self.vat.nbytes + self.ops.nbytes + self.s.nbytes | 
				
			||||
        return f'<WaveSim {self.circuit.name} sims={self.sims} ops={len(self.ops)} ' + \ | 
				
			||||
               f'levels={len(self.level_starts)} mem={hr_bytes(total_mem)}>' | 
				
			||||
 | 
				
			||||
    def get_line_delay(self, line, polarity): | 
				
			||||
        """Returns the current delay of the given ``line`` and ``polarity`` in the simulation model.""" | 
				
			||||
        return self.timing[line, 0, polarity] | 
				
			||||
 | 
				
			||||
    def set_line_delay(self, line, polarity, delay): | 
				
			||||
        """Sets a new ``delay`` for the given ``line`` and ``polarity`` in the simulation model.""" | 
				
			||||
        self.timing[line, 0, polarity] = delay | 
				
			||||
 | 
				
			||||
    def s_to_c(self): | 
				
			||||
        """Transfers values of sequential elements and primary inputs to the combinational portion. | 
				
			||||
 | 
				
			||||
        Based on the data in ``self.s``, waveforms are generated on the input lines of the circuit. | 
				
			||||
        It modifies ``self.c``. | 
				
			||||
        """ | 
				
			||||
        sins = np.moveaxis(self.s[self.pippi_s_locs], -1, 0) | 
				
			||||
        cond = (sins[2] != 0) + 2*(sins[0] != 0)  # choices order: 0 R F 1 | 
				
			||||
        self.c[self.pippi_c_locs] = np.choose(cond, [TMAX, sins[1], TMIN, TMIN]) | 
				
			||||
        self.c[self.pippi_c_locs+1] = np.choose(cond, [TMAX, TMAX, sins[1], TMAX]) | 
				
			||||
        self.c[self.pippi_c_locs+2] = TMAX | 
				
			||||
 | 
				
			||||
    def c_prop(self, sims=None, sd=0.0, seed=1): | 
				
			||||
        """Propagates all waveforms from the (pseudo) primary inputs to the (pseudo) primary outputs. | 
				
			||||
 | 
				
			||||
        :param sims: Number of parallel simulations to execute. If None, all available simulations are performed. | 
				
			||||
        :param sd: Standard deviation for injection of random delay variation. Active, if value is positive. | 
				
			||||
        :param seed: Random seed for delay variations. | 
				
			||||
        """ | 
				
			||||
        sims = min(sims or self.sims, self.sims) | 
				
			||||
        for op_start, op_stop in zip(self.level_starts, self.level_stops): | 
				
			||||
            self.overflows += level_eval(self.ops, op_start, op_stop, self.c, self.vat, 0, sims, | 
				
			||||
                                         self.timing, self.params, sd, seed) | 
				
			||||
        self.lst_eat_valid = False | 
				
			||||
 | 
				
			||||
    def c_to_s(self, time=TMAX, sd=0.0, seed=1): | 
				
			||||
        """Simulates a capture operation at all sequential elements and primary outputs. | 
				
			||||
 | 
				
			||||
        Propagated waveforms in ``self.c`` at and around the given capture time are analyzed and | 
				
			||||
        the results are stored in ``self.s``. | 
				
			||||
 | 
				
			||||
        :param time: The desired capture time. By default, a capture of the settled value is performed. | 
				
			||||
        :param sd: A standard deviation for uncertainty in the actual capture time. | 
				
			||||
        :param seed: The random seed for a capture with uncertainty. | 
				
			||||
        """ | 
				
			||||
        for s_loc, (c_loc, c_len, _) in zip(self.poppo_s_locs, self.vat[self.ppo_offset+self.poppo_s_locs]): | 
				
			||||
            for vector in range(self.sims): | 
				
			||||
                self.s[s_loc, vector, 3:] = self.wave_capture(self.c, c_loc, c_len, vector, time=time, sd=sd, seed=seed) | 
				
			||||
 | 
				
			||||
    def s_ppo_to_ppi(self, time=0.0): | 
				
			||||
        """Re-assigns the last sampled capture to the appropriate pseudo-primary inputs (PPI).  | 
				
			||||
        Each PPI transition is constructed from its previous final value, the | 
				
			||||
        given time, and the sampled captured value of its PPO. Reads and modifies ``self.s``. | 
				
			||||
 | 
				
			||||
        :param time: The transition time at the inputs (usually 0.0). | 
				
			||||
        """ | 
				
			||||
        self.s[self.ppio_s_locs, :, 0] = self.s[self.ppio_s_locs, :, 2] | 
				
			||||
        self.s[self.ppio_s_locs, :, 1] = time | 
				
			||||
        self.s[self.ppio_s_locs, :, 2] = self.s[self.ppio_s_locs, :, 8] | 
				
			||||
 | 
				
			||||
    @staticmethod | 
				
			||||
    def wave_capture(c, c_loc, c_len, vector, time=TMAX, sd=0.0, seed=1): | 
				
			||||
        s_sqrt2 = sd * math.sqrt(2) | 
				
			||||
        m = 0.5 | 
				
			||||
        acc = 0.0 | 
				
			||||
        eat = TMAX | 
				
			||||
        lst = TMIN | 
				
			||||
        tog = 0 | 
				
			||||
        ovl = 0 | 
				
			||||
        val = int(0) | 
				
			||||
        final = int(0) | 
				
			||||
        w = c[c_loc:c_loc+c_len, vector] | 
				
			||||
        for t in w: | 
				
			||||
            if t >= TMAX: | 
				
			||||
                if t == TMAX_OVL: | 
				
			||||
                    ovl = 1 | 
				
			||||
                break | 
				
			||||
            m = -m | 
				
			||||
            final ^= 1 | 
				
			||||
            if t < time: | 
				
			||||
                val ^= 1 | 
				
			||||
            if t <= TMIN: continue | 
				
			||||
            if s_sqrt2 > 0: | 
				
			||||
                acc += m * (1 + math.erf((t - time) / s_sqrt2)) | 
				
			||||
            eat = min(eat, t) | 
				
			||||
            lst = max(lst, t) | 
				
			||||
            tog += 1 | 
				
			||||
        if s_sqrt2 > 0: | 
				
			||||
            if m < 0: | 
				
			||||
                acc += 1 | 
				
			||||
            if acc >= 0.99: | 
				
			||||
                val = 1 | 
				
			||||
            elif acc > 0.01: | 
				
			||||
                seed = (seed << 4) + (vector << 20) + c_loc | 
				
			||||
                seed = int(0xDEECE66D) * seed + 0xB | 
				
			||||
                seed = int(0xDEECE66D) * seed + 0xB | 
				
			||||
                rnd = float((seed >> 8) & 0xffffff) / float(1 << 24) | 
				
			||||
                val = rnd < acc | 
				
			||||
            else: | 
				
			||||
                val = 0 | 
				
			||||
        else: | 
				
			||||
            acc = val | 
				
			||||
 | 
				
			||||
        return (w[0] <= TMIN), eat, lst, final, acc, val, 0, ovl | 
				
			||||
 | 
				
			||||
 | 
				
			||||
@numba.njit | 
				
			||||
def level_eval(ops, op_start, op_stop, c, vat, st_start, st_stop, line_times, params, sd, seed): | 
				
			||||
    overflows = 0 | 
				
			||||
    for op_idx in range(op_start, op_stop): | 
				
			||||
        op = ops[op_idx] | 
				
			||||
        for st_idx in range(st_start, st_stop): | 
				
			||||
            overflows += wave_eval(op, c, vat, st_idx, line_times, params[st_idx], sd, seed) | 
				
			||||
    return overflows | 
				
			||||
 | 
				
			||||
 | 
				
			||||
@numba.njit | 
				
			||||
def rand_gauss(seed, sd): | 
				
			||||
    clamp = 0.5 | 
				
			||||
    if sd <= 0.0: | 
				
			||||
        return 1.0 | 
				
			||||
    while True: | 
				
			||||
        x = -6.0 | 
				
			||||
        for _ in range(12): | 
				
			||||
            seed = int(0xDEECE66D) * seed + 0xB | 
				
			||||
            x += float((seed >> 8) & 0xffffff) / float(1 << 24) | 
				
			||||
        x *= sd | 
				
			||||
        if abs(x) <= clamp: | 
				
			||||
            break | 
				
			||||
    return x + 1.0 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
@numba.njit | 
				
			||||
def wave_eval(op, cbuf, vat, st_idx, line_times, param, sd=0.0, seed=0): | 
				
			||||
    lut, z_idx, a_idx, b_idx, c_idx, d_idx = op | 
				
			||||
    overflows = int(0) | 
				
			||||
 | 
				
			||||
    _seed = (seed << 4) + (z_idx << 20) + (st_idx << 1) | 
				
			||||
 | 
				
			||||
    a_mem = vat[a_idx, 0] | 
				
			||||
    b_mem = vat[b_idx, 0] | 
				
			||||
    c_mem = vat[c_idx, 0] | 
				
			||||
    d_mem = vat[d_idx, 0] | 
				
			||||
    z_mem, z_cap, _ = vat[z_idx] | 
				
			||||
 | 
				
			||||
    a_cur = int(0) | 
				
			||||
    b_cur = int(0) | 
				
			||||
    c_cur = int(0) | 
				
			||||
    d_cur = int(0)                                           | 
				
			||||
    z_cur = lut & 1 | 
				
			||||
    if z_cur == 1: | 
				
			||||
        cbuf[z_mem, st_idx] = TMIN | 
				
			||||
 | 
				
			||||
    a = cbuf[a_mem, st_idx] + line_times[a_idx, 0, z_cur] * rand_gauss(_seed ^ a_mem ^ z_cur, sd) * param[0] | 
				
			||||
    if int(param[1]) == a_idx: a += param[2+z_cur] | 
				
			||||
    b = cbuf[b_mem, st_idx] + line_times[b_idx, 0, z_cur] * rand_gauss(_seed ^ b_mem ^ z_cur, sd) * param[0] | 
				
			||||
    if int(param[1]) == b_idx: b += param[2+z_cur] | 
				
			||||
    c = cbuf[c_mem, st_idx] + line_times[c_idx, 0, z_cur] * rand_gauss(_seed ^ c_mem ^ z_cur, sd) * param[0] | 
				
			||||
    if int(param[1]) == c_idx: c += param[2+z_cur] | 
				
			||||
    d = cbuf[d_mem, st_idx] + line_times[d_idx, 0, z_cur] * rand_gauss(_seed ^ d_mem ^ z_cur, sd) * param[0] | 
				
			||||
    if int(param[1]) == d_idx: d += param[2+z_cur] | 
				
			||||
     | 
				
			||||
    previous_t = TMIN | 
				
			||||
 | 
				
			||||
    current_t = min(a, b, c, d) | 
				
			||||
    inputs = int(0) | 
				
			||||
 | 
				
			||||
    while current_t < TMAX: | 
				
			||||
        z_val = z_cur & 1 | 
				
			||||
        if a == current_t: | 
				
			||||
            a_cur += 1 | 
				
			||||
            a = cbuf[a_mem + a_cur, st_idx] | 
				
			||||
            a += line_times[a_idx, 0, z_val ^ 1] * rand_gauss(_seed ^ a_mem ^ z_val ^ 1, sd) * param[0] | 
				
			||||
            thresh = line_times[a_idx, 1, z_val] * rand_gauss(_seed ^ a_mem ^ z_val, sd) * param[0] | 
				
			||||
            if int(param[1]) == a_idx: | 
				
			||||
                a += param[2+(z_val^1)] | 
				
			||||
                thresh += param[2+z_val] | 
				
			||||
            inputs ^= 1 | 
				
			||||
            next_t = a    | 
				
			||||
         | 
				
			||||
        elif b == current_t: | 
				
			||||
            b_cur += 1 | 
				
			||||
            b = cbuf[b_mem + b_cur, st_idx] | 
				
			||||
            b += line_times[b_idx, 0, z_val ^ 1] * rand_gauss(_seed ^ b_mem ^ z_val ^ 1, sd) * param[0] | 
				
			||||
            thresh = line_times[b_idx, 1, z_val] * rand_gauss(_seed ^ b_mem ^ z_val, sd) * param[0] | 
				
			||||
            if int(param[1]) == b_idx: | 
				
			||||
                b += param[2+(z_val^1)] | 
				
			||||
                thresh += param[2+z_val] | 
				
			||||
            inputs ^= 2 | 
				
			||||
            next_t = b | 
				
			||||
                 | 
				
			||||
        elif c == current_t: | 
				
			||||
            c_cur += 1 | 
				
			||||
            c = cbuf[c_mem + c_cur, st_idx] | 
				
			||||
            c += line_times[c_idx, 0, z_val ^ 1] * rand_gauss(_seed ^ c_mem ^ z_val ^ 1, sd) * param[0] | 
				
			||||
            thresh = line_times[c_idx, 1, z_val] * rand_gauss(_seed ^ c_mem ^ z_val, sd) * param[0] | 
				
			||||
            if int(param[1]) == c_idx: | 
				
			||||
                c += param[2+(z_val^1)] | 
				
			||||
                thresh += param[2+z_val] | 
				
			||||
            inputs ^= 4 | 
				
			||||
            next_t = c  | 
				
			||||
                      | 
				
			||||
        else: | 
				
			||||
            d_cur += 1 | 
				
			||||
            d = cbuf[d_mem + d_cur, st_idx] | 
				
			||||
            d += line_times[d_idx, 0, z_val ^ 1] * rand_gauss(_seed ^ d_mem ^ z_val ^ 1, sd) * param[0] | 
				
			||||
            thresh = line_times[d_idx, 1, z_val] * rand_gauss(_seed ^ d_mem ^ z_val, sd) * param[0] | 
				
			||||
            if int(param[1]) == d_idx: | 
				
			||||
                d += param[2+(z_val^1)] | 
				
			||||
                thresh += param[2+z_val] | 
				
			||||
            inputs ^= 8 | 
				
			||||
            next_t = d  | 
				
			||||
        #print("previous_t",previous_t) | 
				
			||||
        #print("current_t",current_t)  | 
				
			||||
        #print(current_t - previous_t) | 
				
			||||
        #print(thresh) | 
				
			||||
        #print(z_cur & 1) | 
				
			||||
        #print((lut >> inputs) & 1) | 
				
			||||
        | 
				
			||||
        if (z_cur & 1) != ((lut >> inputs) & 1): | 
				
			||||
            # we generate a toggle in z_mem, if: | 
				
			||||
            #   ( it is the first toggle in z_mem OR | 
				
			||||
            #   following toggle is earlier OR | 
				
			||||
            #   pulse is wide enough ) AND enough space in z_mem. | 
				
			||||
            if z_cur == 0 or next_t < current_t or (current_t - previous_t) > thresh: | 
				
			||||
                #print(current_t - previous_t) | 
				
			||||
                #print(thresh) | 
				
			||||
                #print(z_cap) | 
				
			||||
                if z_cur < (z_cap - 1): | 
				
			||||
                    cbuf[z_mem + z_cur, st_idx] = current_t | 
				
			||||
                    #print(cbuf[z_mem + z_cur, st_idx]) | 
				
			||||
                    previous_t = current_t | 
				
			||||
                    z_cur += 1 | 
				
			||||
                else: | 
				
			||||
                    overflows += 1 | 
				
			||||
                    previous_t = cbuf[z_mem + z_cur - 1, st_idx] | 
				
			||||
                    z_cur -= 1 | 
				
			||||
            else: | 
				
			||||
                #print(a) | 
				
			||||
                z_cur -= 1 | 
				
			||||
                if z_cur > 0: | 
				
			||||
                    previous_t = cbuf[z_mem + z_cur - 1, st_idx] | 
				
			||||
                else: | 
				
			||||
                    previous_t = TMIN | 
				
			||||
         | 
				
			||||
        current_t = min(a, b, c, d) | 
				
			||||
                      | 
				
			||||
    if overflows > 0: | 
				
			||||
        cbuf[z_mem + z_cur, st_idx] = TMAX_OVL | 
				
			||||
    else: | 
				
			||||
        cbuf[z_mem + z_cur, st_idx] = a if a == max(a, b, c, d) else b if b == max(a, b, c, d) else c if c == max(a, b, c, d) else d   # propagate overflow flags by storing biggest TMAX from input | 
				
			||||
 | 
				
			||||
    return overflows | 
				
			||||
@ -0,0 +1,174 @@
				@@ -0,0 +1,174 @@
					 | 
				
			||||
import numpy as np | 
				
			||||
 | 
				
			||||
from kyupy.wave_sim4 import WaveSim, wave_eval, TMIN, TMAX | 
				
			||||
from kyupy.logic_sim import LogicSim | 
				
			||||
from kyupy import verilog, sdf, logic, bench | 
				
			||||
from kyupy.logic import MVArray, BPArray | 
				
			||||
from kyupy.sim import SimPrim | 
				
			||||
 | 
				
			||||
 | 
				
			||||
def test_nand_delays(): | 
				
			||||
    op = (SimPrim.NAND4, 4, 0, 1, 2, 3) | 
				
			||||
    #op = (0b0111, 4, 0, 1) | 
				
			||||
    c = np.full((5*16, 1), TMAX)  # 5 waveforms of capacity 16 | 
				
			||||
    vat = np.zeros((5, 3), dtype='int') | 
				
			||||
    for i in range(5): vat[i] = i*16, 16, 0  # 1:1 mapping | 
				
			||||
 | 
				
			||||
    # SDF specifies IOPATH delays with respect to output polarity | 
				
			||||
    # SDF pulse rejection value is determined by IOPATH causing last transition and polarity of last transition | 
				
			||||
    line_times = np.zeros((5, 2, 2)) | 
				
			||||
    line_times[0, 0, 0] = 0.1  # A -> Z rise delay | 
				
			||||
    line_times[0, 0, 1] = 0.2  # A -> Z fall delay | 
				
			||||
    line_times[0, 1, 0] = 0.1  # A -> Z negative pulse limit (terminate in rising Z) | 
				
			||||
    line_times[0, 1, 1] = 0.2  # A -> Z positive pulse limit | 
				
			||||
    line_times[1, :, 0] = 0.3  # as above for B -> Z | 
				
			||||
    line_times[1, :, 1] = 0.4 | 
				
			||||
    line_times[2, :, 0] = 0.5  # as above for C -> Z | 
				
			||||
    line_times[2, :, 1] = 0.6 | 
				
			||||
    line_times[3, :, 0] = 0.7  # as above for D -> Z | 
				
			||||
    line_times[3, :, 1] = 0.8 | 
				
			||||
     | 
				
			||||
    sdata = np.asarray([1, -1, 0, 0], dtype='float32') | 
				
			||||
 | 
				
			||||
    def wave_assert(inputs, output): | 
				
			||||
        for i, a in zip(inputs, c.reshape(-1,16)): a[:len(i)] = i | 
				
			||||
        wave_eval(op, c, vat, 0, line_times, sdata) | 
				
			||||
        for i, v in enumerate(output): np.testing.assert_allclose(c.reshape(-1,16)[4,i], v) | 
				
			||||
 | 
				
			||||
    wave_assert([[TMAX,TMAX],[TMAX,TMAX],[TMIN,TMAX],[TMIN,TMAX]], [TMIN,TMAX]) # NAND(0,0,1,1) => 1 | 
				
			||||
    wave_assert([[TMIN,TMAX],[TMAX,TMAX],[TMIN,TMAX],[TMIN,TMAX]], [TMIN,TMAX]) # NAND(1,0,1,1) => 1 | 
				
			||||
    wave_assert([[TMIN,TMAX],[TMIN,TMAX],[TMIN,TMAX],[TMIN,TMAX]], [TMAX])      # NAND(1,1,1,1) => 0 | 
				
			||||
 | 
				
			||||
    # Keep inputs C=1 and D=1. | 
				
			||||
    wave_assert([[1,TMAX],[2,TMAX]], [TMIN,2.4,TMAX])              # _/⎺⎺⎺ NAND __/⎺⎺ => ⎺⎺⎺\___ (B->Z fall delay) | 
				
			||||
    wave_assert([[TMIN,TMAX],[TMIN,2,TMAX]],  [2.3,TMAX])          # ⎺⎺⎺⎺⎺ NAND ⎺⎺\__ => ___/⎺⎺⎺ (B->Z rise delay) | 
				
			||||
    wave_assert([[TMIN,TMAX],[TMIN,2,2.35,TMAX]], [2.3,2.75,TMAX]) # ⎺⎺⎺⎺⎺ NAND ⎺\_/⎺ => __/⎺⎺\_ (pos pulse, .35@B -> .45@Z) | 
				
			||||
    wave_assert([[TMIN,TMAX],[TMIN,2,2.25,TMAX]], [TMAX])          # ⎺⎺⎺⎺⎺ NAND ⎺\_/⎺ => _______ (pos pulse, .25@B -> .35@Z, filtered) | 
				
			||||
    wave_assert([[TMIN,TMAX],[2,2.45,TMAX]], [TMIN,2.4,2.75,TMAX]) # ⎺⎺⎺⎺⎺ NAND _/⎺\_ => ⎺⎺\_/⎺⎺ (neg pulse, .45@B -> .35@Z) | 
				
			||||
    wave_assert([[TMIN,TMAX],[2,2.35,TMAX]], [TMIN,TMAX])          # ⎺⎺⎺⎺⎺ NAND _/⎺\_ => ⎺⎺⎺⎺⎺⎺⎺ (neg pulse, .35@B -> .25@Z, filtered) | 
				
			||||
 | 
				
			||||
 | 
				
			||||
def test_tiny_circuit(): | 
				
			||||
    c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)') | 
				
			||||
    lt = np.zeros((len(c.lines), 2, 2)) | 
				
			||||
    lt[:,0,:] = 1.0  # unit delay for all lines | 
				
			||||
    wsim = WaveSim(c, lt) | 
				
			||||
    print(wsim.prim_counts) | 
				
			||||
    assert len(wsim.s) == 5 | 
				
			||||
     | 
				
			||||
    # values for x | 
				
			||||
    wsim.s[0,0,:3] = 0, 0.1, 0 | 
				
			||||
    wsim.s[0,1,:3] = 0, 0.2, 1 | 
				
			||||
    wsim.s[0,2,:3] = 1, 0.3, 0 | 
				
			||||
    wsim.s[0,3,:3] = 1, 0.4, 1 | 
				
			||||
 | 
				
			||||
    # values for y | 
				
			||||
    wsim.s[1,0,:3] = 1, 0.5, 0 | 
				
			||||
    wsim.s[1,1,:3] = 1, 0.6, 0 | 
				
			||||
    wsim.s[1,2,:3] = 1, 0.7, 0 | 
				
			||||
    wsim.s[1,3,:3] = 0, 0.8, 1 | 
				
			||||
     | 
				
			||||
    wsim.s_to_c() | 
				
			||||
 | 
				
			||||
    x_c_loc = wsim.vat[wsim.ppi_offset+0, 0] # check x waveforms | 
				
			||||
    np.testing.assert_allclose(wsim.c[x_c_loc:x_c_loc+3, 0], [TMAX, TMAX, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[x_c_loc:x_c_loc+3, 1], [0.2, TMAX, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[x_c_loc:x_c_loc+3, 2], [TMIN, 0.3, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[x_c_loc:x_c_loc+3, 3], [TMIN, TMAX, TMAX]) | 
				
			||||
 | 
				
			||||
    y_c_loc = wsim.vat[wsim.ppi_offset+1, 0] # check y waveforms | 
				
			||||
    np.testing.assert_allclose(wsim.c[y_c_loc:y_c_loc+3, 0], [TMIN, 0.5, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[y_c_loc:y_c_loc+3, 1], [TMIN, 0.6, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[y_c_loc:y_c_loc+3, 2], [TMIN, 0.7, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[y_c_loc:y_c_loc+3, 3], [0.8, TMAX, TMAX]) | 
				
			||||
 | 
				
			||||
    wsim.c_prop() | 
				
			||||
 | 
				
			||||
    a_c_loc = wsim.vat[wsim.ppo_offset+2, 0] # check a waveforms | 
				
			||||
    np.testing.assert_allclose(wsim.c[a_c_loc:a_c_loc+3, 0], [TMAX, TMAX, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[a_c_loc:a_c_loc+3, 1], [1.2, 1.6, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[a_c_loc:a_c_loc+3, 2], [TMIN, 1.3, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[a_c_loc:a_c_loc+3, 3], [1.8, TMAX, TMAX]) | 
				
			||||
 | 
				
			||||
    o_c_loc = wsim.vat[wsim.ppo_offset+3, 0] # check o waveforms | 
				
			||||
    np.testing.assert_allclose(wsim.c[o_c_loc:o_c_loc+3, 0], [TMIN, 1.5, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[o_c_loc:o_c_loc+3, 1], [TMIN, TMAX, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[o_c_loc:o_c_loc+3, 2], [TMIN, 1.7, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[o_c_loc:o_c_loc+3, 3], [TMIN, TMAX, TMAX]) | 
				
			||||
     | 
				
			||||
    n_c_loc = wsim.vat[wsim.ppo_offset+4, 0] # check n waveforms | 
				
			||||
    np.testing.assert_allclose(wsim.c[n_c_loc:n_c_loc+3, 0], [TMIN, TMAX, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[n_c_loc:n_c_loc+3, 1], [TMIN, 1.2, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[n_c_loc:n_c_loc+3, 2], [1.3, TMAX, TMAX]) | 
				
			||||
    np.testing.assert_allclose(wsim.c[n_c_loc:n_c_loc+3, 3], [TMAX, TMAX, TMAX]) | 
				
			||||
 | 
				
			||||
    wsim.c_to_s() | 
				
			||||
 | 
				
			||||
    # check a captures | 
				
			||||
    np.testing.assert_allclose(wsim.s[2, 0, 3:7], [0, TMAX, TMIN, 0]) | 
				
			||||
    np.testing.assert_allclose(wsim.s[2, 1, 3:7], [0, 1.2, 1.6, 0]) | 
				
			||||
    np.testing.assert_allclose(wsim.s[2, 2, 3:7], [1, 1.3, 1.3, 0]) | 
				
			||||
    np.testing.assert_allclose(wsim.s[2, 3, 3:7], [0, 1.8, 1.8, 1]) | 
				
			||||
 | 
				
			||||
    # check o captures | 
				
			||||
    np.testing.assert_allclose(wsim.s[3, 0, 3:7], [1, 1.5, 1.5, 0]) | 
				
			||||
    np.testing.assert_allclose(wsim.s[3, 1, 3:7], [1, TMAX, TMIN, 1]) | 
				
			||||
    np.testing.assert_allclose(wsim.s[3, 2, 3:7], [1, 1.7, 1.7, 0]) | 
				
			||||
    np.testing.assert_allclose(wsim.s[3, 3, 3:7], [1, TMAX, TMIN, 1]) | 
				
			||||
 | 
				
			||||
    # check o captures | 
				
			||||
    np.testing.assert_allclose(wsim.s[4, 0, 3:7], [1, TMAX, TMIN, 1]) | 
				
			||||
    np.testing.assert_allclose(wsim.s[4, 1, 3:7], [1, 1.2, 1.2, 0]) | 
				
			||||
    np.testing.assert_allclose(wsim.s[4, 2, 3:7], [0, 1.3, 1.3, 1]) | 
				
			||||
    np.testing.assert_allclose(wsim.s[4, 3, 3:7], [0, TMAX, TMIN, 0]) | 
				
			||||
 | 
				
			||||
 | 
				
			||||
def compare_to_logic_sim(wsim: WaveSim): | 
				
			||||
    tests = MVArray((len(wsim.s_nodes), wsim.sims)) | 
				
			||||
    choices = np.asarray([logic.ZERO, logic.ONE, logic.RISE, logic.FALL], dtype=np.uint8) | 
				
			||||
    rng = np.random.default_rng(10) | 
				
			||||
    tests.data[...] = rng.choice(choices, tests.data.shape) | 
				
			||||
 | 
				
			||||
    wsim.s[..., 0] = (tests.data & 2) >> 1 | 
				
			||||
    wsim.s[..., 3] = (tests.data & 2) >> 1 | 
				
			||||
    wsim.s[..., 1] = 0.0 | 
				
			||||
    wsim.s[..., 2] = tests.data & 1 | 
				
			||||
    wsim.s[..., 6] = tests.data & 1 | 
				
			||||
     | 
				
			||||
    wsim.s_to_c() | 
				
			||||
    wsim.c_prop() | 
				
			||||
    wsim.c_to_s() | 
				
			||||
 | 
				
			||||
    resp = MVArray(tests) | 
				
			||||
    resp.data[...] = wsim.s[..., 6].astype(np.uint8) | (wsim.s[..., 3].astype(np.uint8)<<1) | 
				
			||||
    resp.data |= ((resp.data ^ (resp.data >> 1)) & 1) << 2  # transitions | 
				
			||||
 | 
				
			||||
    tests_bp = BPArray(tests)     | 
				
			||||
    lsim = LogicSim(wsim.circuit, len(tests_bp)) | 
				
			||||
    lsim.assign(tests_bp) | 
				
			||||
    lsim.propagate() | 
				
			||||
    exp_bp = BPArray(tests_bp) | 
				
			||||
    lsim.capture(exp_bp) | 
				
			||||
    exp = MVArray(exp_bp) | 
				
			||||
 | 
				
			||||
    for i in range(8): | 
				
			||||
        exp_str = exp[i].replace('P', '0').replace('N', '1') | 
				
			||||
        res_str = resp[i].replace('P', '0').replace('N', '1') | 
				
			||||
        assert res_str == exp_str | 
				
			||||
 | 
				
			||||
 | 
				
			||||
def test_b14(mydir): | 
				
			||||
    c = verilog.load(mydir / 'b14.v.gz', branchforks=True) | 
				
			||||
    df = sdf.load(mydir / 'b14.sdf.gz') | 
				
			||||
    lt = df.annotation(c) | 
				
			||||
    wsim = WaveSim(c, lt, 8) | 
				
			||||
    compare_to_logic_sim(wsim) | 
				
			||||
 | 
				
			||||
 | 
				
			||||
def test_b14_strip_forks(mydir): | 
				
			||||
    c = verilog.load(mydir / 'b14.v.gz', branchforks=True) | 
				
			||||
    df = sdf.load(mydir / 'b14.sdf.gz') | 
				
			||||
    lt = df.annotation(c) | 
				
			||||
    wsim = WaveSim(c, lt, 8, strip_forks=True) | 
				
			||||
    compare_to_logic_sim(wsim) | 
				
			||||
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue