doc improvements
This commit is contained in:
1 parent
dc76a9f517
commit
0b15f9fa18
11 files changed
+217
-186
No files matched your search
@@ -1944,7 +1944,7 @@
|
||||
"from kyupy import sdf\n",
|
||||
"\n",
|
||||
"df = sdf.load('../tests/b15_2ig.sdf.gz')\n",
|
||||
"delays = df.annotation(b15, dataset=0, interconnect=False)"
|
||||
"delays = df.iopaths(b15)[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1989,7 +1989,7 @@
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"78296"
|
||||
"79010"
|
||||
]
|
||||
},
|
||||
"execution_count": 64,
|
||||
@@ -2209,7 +2209,7 @@
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1.1518999338150024"
|
||||
"1.0424000024795532"
|
||||
]
|
||||
},
|
||||
"execution_count": 71,
|
||||
|
||||
+2
-2
@@ -57,8 +57,8 @@ def parse(text, name=None):
|
||||
def load(file, name=None):
|
||||
"""Parses the contents of ``file`` as ISCAS89 bench code.
|
||||
|
||||
:param file: The file to be loaded.
|
||||
:param name: The name of the circuit. If none given, the file name is used as circuit name.
|
||||
:param file: The file to be loaded. Files with `.gz`-suffix are decompressed on-the-fly.
|
||||
:param name: The name of the circuit. If None, the file name is used as circuit name.
|
||||
:return: A :class:`Circuit` object.
|
||||
"""
|
||||
return parse(readtext(file), name=name or str(file))
|
||||
+86
-80
@@ -65,9 +65,9 @@ class Node:
|
||||
self.index = len(circuit.nodes) - 1
|
||||
"""A unique and consecutive integer index of the node within the circuit.
|
||||
|
||||
It can be used to store additional data about the node :code:`n`
|
||||
It can be used to associate additional data to a node :code:`n`
|
||||
by allocating an array or list :code:`my_data` of length :code:`len(n.circuit.nodes)` and
|
||||
accessing it by :code:`my_data[n.index]`.
|
||||
accessing it by :code:`my_data[n.index]` or simply by :code:`my_data[n]`.
|
||||
"""
|
||||
self.ins = GrowingList()
|
||||
"""A list of input connections (:class:`Line` objects).
|
||||
@@ -136,7 +136,7 @@ class Line:
|
||||
|
||||
It can be used to store additional data about the line :code:`l`
|
||||
by allocating an array or list :code:`my_data` of length :code:`len(l.circuit.lines)` and
|
||||
accessing it by :code:`my_data[l.index]`.
|
||||
accessing it by :code:`my_data[l.index]` or simply by :code:`my_data[l]`.
|
||||
"""
|
||||
if not isinstance(driver, tuple): driver = (driver, driver.outs.free_index())
|
||||
self.driver = driver[0]
|
||||
@@ -145,7 +145,7 @@ class Line:
|
||||
self.driver_pin = driver[1]
|
||||
"""The output pin position of the driver node this line is connected to.
|
||||
|
||||
This is the position in the outs-list of the driving node this line referenced from:
|
||||
This is the position in the list :py:attr:`Node.outs` of the driving node this line referenced from:
|
||||
:code:`self.driver.outs[self.driver_pin] == self`.
|
||||
"""
|
||||
if not isinstance(reader, tuple): reader = (reader, reader.ins.free_index())
|
||||
@@ -155,7 +155,7 @@ class Line:
|
||||
self.reader_pin = reader[1]
|
||||
"""The input pin position of the reader node this line is connected to.
|
||||
|
||||
This is the position in the ins-list of the reader node this line referenced from:
|
||||
This is the position in the list :py:attr:`Node.ins` of the reader node this line referenced from:
|
||||
:code:`self.reader.ins[self.reader_pin] == self`.
|
||||
"""
|
||||
self.driver.outs[self.driver_pin] = self
|
||||
@@ -209,17 +209,21 @@ class Circuit:
|
||||
self.name = name
|
||||
"""The name of the circuit.
|
||||
"""
|
||||
self.nodes = IndexList()
|
||||
self.nodes : list[Node] = IndexList()
|
||||
"""A list of all :class:`Node` objects contained in the circuit.
|
||||
|
||||
The position of a node in this list equals its index :code:`self.nodes[42].index == 42`.
|
||||
This list should not be changed directly.
|
||||
Use the :class:`Node` constructor and :py:attr:`Node.remove()` to add and remove nodes.
|
||||
"""
|
||||
self.lines = IndexList()
|
||||
self.lines : list[Line] = IndexList()
|
||||
"""A list of all :class:`Line` objects contained in the circuit.
|
||||
|
||||
The position of a line in this list equals its index :code:`self.lines[42].index == 42`.
|
||||
This list should not be changed directly.
|
||||
Use the :class:`Line` constructor and :py:attr:`Line.remove()` to add and remove lines.
|
||||
"""
|
||||
self.io_nodes = GrowingList()
|
||||
self.io_nodes : list[Node] = GrowingList()
|
||||
"""A list of nodes that are designated as primary input- or output-ports.
|
||||
|
||||
Port-nodes are contained in :py:attr:`nodes` as well as :py:attr:`io_nodes`.
|
||||
@@ -237,8 +241,74 @@ class Circuit:
|
||||
|
||||
@property
|
||||
def s_nodes(self):
|
||||
"""A list of all io_nodes as well as all flip-flops and latches in the circuit (in that order).
|
||||
"""
|
||||
return list(self.io_nodes) + [n for n in self.nodes if 'dff' in n.kind.lower()] + [n for n in self.nodes if 'latch' in n.kind.lower()]
|
||||
|
||||
def io_locs(self, prefix):
|
||||
"""Returns the indices of primary I/Os that start with given name prefix.
|
||||
|
||||
The returned values are used to index into the :py:attr:`io_nodes` array.
|
||||
If only one I/O cell matches the given prefix, a single integer is returned.
|
||||
If a bus matches the given prefix, a sorted list of indices is returned.
|
||||
Busses are identified by integers in the cell names following the given prefix.
|
||||
Lists for bus indices are sorted from LSB (e.g. :code:`data[0]`) to MSB (e.g. :code:`data[31]`).
|
||||
If a prefix matches multiple different signals or busses, alphanumerically sorted
|
||||
lists of lists are returned. Therefore, higher-dimensional busses
|
||||
(e.g. :code:`data0[0], data0[1], ...`, :code:`data1[0], data1[1], ...`) are supported as well.
|
||||
"""
|
||||
return self._locs(prefix, list(self.io_nodes))
|
||||
|
||||
def s_locs(self, prefix):
|
||||
"""Returns the indices of I/Os and sequential elements that start with given name prefix.
|
||||
|
||||
The returned values are used to index into the :py:attr:`s_nodes` array.
|
||||
It works the same as :py:attr:`io_locs`. See there for more details.
|
||||
"""
|
||||
return self._locs(prefix, self.s_nodes)
|
||||
|
||||
def _locs(self, prefix, nodes):
|
||||
d_top = dict()
|
||||
for i, n in enumerate(nodes):
|
||||
if m := re.match(fr'({prefix}.*?)((?:[\d_\[\]])*$)', n.name):
|
||||
path = [m[1]] + [int(v) for v in re.split(r'[_\[\]]+', m[2]) if len(v) > 0]
|
||||
d = d_top
|
||||
for j in path[:-1]:
|
||||
d[j] = d.get(j, dict())
|
||||
d = d[j]
|
||||
d[path[-1]] = i
|
||||
|
||||
# sort recursively for multi-dimensional lists.
|
||||
def sorted_values(d): return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d
|
||||
l = sorted_values(d_top)
|
||||
while isinstance(l, list) and len(l) == 1: l = l[0]
|
||||
return None if isinstance(l, list) and len(l) == 0 else l
|
||||
|
||||
@property
|
||||
def stats(self):
|
||||
"""A dictionary with the number of all different elements in the circuit.
|
||||
|
||||
The dictionary contains the number of all different kinds of nodes, the number
|
||||
of lines, as well various sums like number of combinational gates, number of
|
||||
primary I/Os, number of sequential elements, and so on.
|
||||
|
||||
The count of regular cells use their :py:attr:`Node.kind` as key, other statistics use
|
||||
dunder-keys like: `__comb__`, `__io__`, `__seq__`, and so on.
|
||||
"""
|
||||
stats = defaultdict(int)
|
||||
stats['__node__'] = len(self.nodes)
|
||||
stats['__cell__'] = len(self.cells)
|
||||
stats['__fork__'] = len(self.forks)
|
||||
stats['__io__'] = len(self.io_nodes)
|
||||
stats['__line__'] = len(self.lines)
|
||||
for n in self.cells.values():
|
||||
stats[n.kind] += 1
|
||||
if 'dff' in n.kind.lower(): stats['__dff__'] += 1
|
||||
elif 'latch' in n.kind.lower(): stats['__latch__'] += 1
|
||||
elif 'put' not in n.kind.lower(): stats['__comb__'] += 1 # no input or output
|
||||
stats['__seq__'] = stats['__dff__'] + stats['__latch__']
|
||||
return dict(stats)
|
||||
|
||||
def get_or_add_fork(self, name):
|
||||
return self.forks[name] if name in self.forks else Node(self, name)
|
||||
|
||||
@@ -289,46 +359,21 @@ class Circuit:
|
||||
def __repr__(self):
|
||||
return f'{{name: "{self.name}", cells: {len(self.cells)}, forks: {len(self.forks)}, lines: {len(self.lines)}, io_nodes: {len(self.io_nodes)}}}'
|
||||
|
||||
@property
|
||||
def stats(self):
|
||||
"""Generates a dictionary with the number of all different elements in the circuit.
|
||||
|
||||
The dictionary contains the number of all different kinds of nodes, the number
|
||||
of lines, as well various sums like number of combinational gates, number of
|
||||
primary I/Os, number of sequential elements, and so on.
|
||||
|
||||
The count of regular cells use their `Node.kind` as key, other statistics use
|
||||
dunder-keys like: `__comb__`, `__io__`, `__seq__`, and so on.
|
||||
"""
|
||||
stats = defaultdict(int)
|
||||
stats['__node__'] = len(self.nodes)
|
||||
stats['__cell__'] = len(self.cells)
|
||||
stats['__fork__'] = len(self.forks)
|
||||
stats['__io__'] = len(self.io_nodes)
|
||||
stats['__line__'] = len(self.lines)
|
||||
for n in self.cells.values():
|
||||
stats[n.kind] += 1
|
||||
if 'dff' in n.kind.lower(): stats['__dff__'] += 1
|
||||
elif 'latch' in n.kind.lower(): stats['__latch__'] += 1
|
||||
elif 'put' not in n.kind.lower(): stats['__comb__'] += 1 # no input or output
|
||||
stats['__seq__'] = stats['__dff__'] + stats['__latch__']
|
||||
return dict(stats)
|
||||
|
||||
def topological_order(self):
|
||||
"""Generator function to iterate over all nodes in topological order.
|
||||
|
||||
Nodes without input lines and nodes whose :py:attr:`Node.kind` contains the substring 'DFF' are
|
||||
yielded first.
|
||||
Nodes without input lines and nodes whose :py:attr:`Node.kind` contains the
|
||||
substrings 'dff' or 'latch' are yielded first.
|
||||
"""
|
||||
visit_count = [0] * len(self.nodes)
|
||||
queue = deque(n for n in self.nodes if len(n.ins) == 0 or 'dff' in n.kind.lower())
|
||||
queue = deque(n for n in self.nodes if len(n.ins) == 0 or 'dff' in n.kind.lower() or 'latch' in n.kind.lower())
|
||||
while len(queue) > 0:
|
||||
n = queue.popleft()
|
||||
for line in n.outs:
|
||||
if line is None: continue
|
||||
succ = line.reader
|
||||
visit_count[succ] += 1
|
||||
if visit_count[succ] == len(succ.ins) and 'dff' not in succ.kind.lower():
|
||||
if visit_count[succ] == len(succ.ins) and 'dff' not in succ.kind.lower() and 'latch' not in succ.kind.lower():
|
||||
queue.append(succ)
|
||||
yield n
|
||||
|
||||
@@ -343,17 +388,17 @@ class Circuit:
|
||||
def reversed_topological_order(self):
|
||||
"""Generator function to iterate over all nodes in reversed topological order.
|
||||
|
||||
Nodes without output lines and nodes whose :py:attr:`Node.kind` contains the substring 'DFF' are
|
||||
yielded first.
|
||||
Nodes without output lines and nodes whose :py:attr:`Node.kind` contains the
|
||||
substrings 'dff' or 'latch' are yielded first.
|
||||
"""
|
||||
visit_count = [0] * len(self.nodes)
|
||||
queue = deque(n for n in self.nodes if len(n.outs) == 0 or 'dff' in n.kind.lower())
|
||||
queue = deque(n for n in self.nodes if len(n.outs) == 0 or 'dff' in n.kind.lower() or 'latch' in n.kind.lower())
|
||||
while len(queue) > 0:
|
||||
n = queue.popleft()
|
||||
for line in n.ins:
|
||||
pred = line.driver
|
||||
visit_count[pred] += 1
|
||||
if visit_count[pred] == len(pred.outs) and 'dff' not in pred.kind.lower():
|
||||
if visit_count[pred] == len(pred.outs) and 'dff' not in pred.kind.lower() and 'latch' not in pred.kind.lower():
|
||||
queue.append(pred)
|
||||
yield n
|
||||
|
||||
@@ -393,42 +438,3 @@ class Circuit:
|
||||
queue.extend(preds)
|
||||
region.append(n)
|
||||
yield stem, region
|
||||
|
||||
def io_locs(self, prefix):
|
||||
"""Returns the indices of primary I/Os that start with given name prefix.
|
||||
|
||||
The returned values are used to index into the :py:attr:`io_nodes` array.
|
||||
If only one I/O cell matches the given prefix, a single integer is returned.
|
||||
If a bus matches the given prefix, a sorted list of indices is returned.
|
||||
Busses are identified by integers in the cell names following the given prefix.
|
||||
Lists for bus indices are sorted from LSB (e.g. `data[0]`) to MSB (e.g. `data[31]`).
|
||||
If a prefix matches multiple different signals or busses, alphanumerically sorted
|
||||
lists of lists are returned. Therefore, higher-dimensional busses
|
||||
(e.g. `data0[0], data0[1], ...`, `data1[0], data1[1], ...`) are supported as well.
|
||||
"""
|
||||
return self._locs(prefix, list(self.io_nodes))
|
||||
|
||||
def s_locs(self, prefix):
|
||||
"""Returns the indices of I/Os and sequential elements that start with given name prefix.
|
||||
|
||||
The returned values are used to index into the :py:attr:`s_nodes` array.
|
||||
It works the same as :py:attr:`io_locs`. See there for more details.
|
||||
"""
|
||||
return self._locs(prefix, self.s_nodes)
|
||||
|
||||
def _locs(self, prefix, nodes):
|
||||
d_top = dict()
|
||||
for i, n in enumerate(nodes):
|
||||
if m := re.match(fr'({prefix}.*?)((?:[\d_\[\]])*$)', n.name):
|
||||
path = [m[1]] + [int(v) for v in re.split(r'[_\[\]]+', m[2]) if len(v) > 0]
|
||||
d = d_top
|
||||
for j in path[:-1]:
|
||||
d[j] = d.get(j, dict())
|
||||
d = d[j]
|
||||
d[path[-1]] = i
|
||||
|
||||
# sort recursively for multi-dimensional lists.
|
||||
def sorted_values(d): return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d
|
||||
l = sorted_values(d_top)
|
||||
while isinstance(l, list) and len(l) == 1: l = l[0]
|
||||
return None if isinstance(l, list) and len(l) == 0 else l
|
||||
+111
-88
@@ -1,4 +1,9 @@
|
||||
"""This module contains definitions and utilities for 2-, 4-, and 8-valued logic operations.
|
||||
"""This module contains definitions and tools for 2-, 4-, and 8-valued logic operations.
|
||||
|
||||
Logic values are stored in numpy arrays with data type ``np.uint8``.
|
||||
There are no explicit data structures in KyuPy for holding patterns, pattern sets or vectors.
|
||||
However, there are conventions on logic value encoding and on the order of axes.
|
||||
Utility functions defined here follow these conventions.
|
||||
|
||||
8 logic values are defined as integer constants.
|
||||
|
||||
@@ -21,10 +26,22 @@ Except when bit0 differs from bit1, but bit2 (activity) is 0:
|
||||
4-valued logic only considers bit0 and bit1.
|
||||
8-valued logic considers all 3 bits.
|
||||
|
||||
Logic values are stored in numpy arrays of data type ``np.uint8``.
|
||||
The axis convention is as follows:
|
||||
* The **last** axis goes along patterns/vectors. I.e. ``values[...,0]`` is pattern 0, ``values[...,1]`` is pattern 1, etc.
|
||||
* The **second-to-last** axis goes along the I/O and flip-flops of circuits. For a circuit ``c``, this axis is usually
|
||||
``len(c.s_nodes)`` long. The values of all inputs, outputs and flip-flops are stored within the same array and the location
|
||||
along the second-to-last axis is determined by the order in ``c.s_nodes``.
|
||||
|
||||
Two storage formats are used in KyuPy:
|
||||
* ``mv...`` (for "multi-valued"): Each logic value is stored in the least significant 3 bits of ``np.uint8``.
|
||||
* ``bp...`` (for "bit-parallel"): Groups of 8 logic values are stored as three ``np.uint8``. This format is used
|
||||
for bit-parallel logic simulations. It is also more memory-efficient.
|
||||
|
||||
The functions in this module use the ``mv...`` and ``bp...`` prefixes to signify the storage format they operate on.
|
||||
|
||||
"""
|
||||
|
||||
import math
|
||||
from collections.abc import Iterable
|
||||
|
||||
import numpy as np
|
||||
@@ -80,41 +97,12 @@ def interpret(value):
|
||||
return UNKNOWN
|
||||
|
||||
|
||||
_bit_in_lut = np.array([2 ** x for x in range(7, -1, -1)], dtype='uint8')
|
||||
|
||||
|
||||
@numba.njit
|
||||
def bit_in(a, pos):
|
||||
return a[pos >> 3] & _bit_in_lut[pos & 7]
|
||||
|
||||
|
||||
def unpackbits(a : np.ndarray):
|
||||
"""Unpacks the bits of given ndarray `a`.
|
||||
|
||||
Similar to `np.unpackbits`, but accepts any dtype, preserves the shape of `a` and
|
||||
adds a new last axis with the bits of each item. Bits are in 'little'-order, i.e.,
|
||||
a[...,0] is the least significant bit.
|
||||
"""
|
||||
return np.unpackbits(a.view(np.uint8), bitorder='little').reshape(*a.shape, 8*a.itemsize)
|
||||
|
||||
|
||||
def packbits(a, dtype=np.uint8):
|
||||
"""Packs the values of a boolean-valued array `a` along its last axis into bits.
|
||||
|
||||
Similary to `np.packbits`, but returns an array of given dtype and the shape of `a` with the last axis removed.
|
||||
The last axis of `a` is truncated or padded according to the bit-width of the given dtype.
|
||||
Signed integer datatypes are padded with the most significant bit, all others are padded with `0`.
|
||||
"""
|
||||
dtype = np.dtype(dtype)
|
||||
bits = 8 * dtype.itemsize
|
||||
a = a[...,:bits]
|
||||
if a.shape[-1] < bits:
|
||||
p = [(0,0)]*(len(a.shape)-1) + [(0, bits-a.shape[-1])]
|
||||
a = np.pad(a, p, 'edge') if dtype.name[0] == 'i' else np.pad(a, p, 'constant', constant_values=0)
|
||||
return np.packbits(a, bitorder='little').view(dtype).reshape(a.shape[:-1])
|
||||
|
||||
|
||||
def mvarray(*a):
|
||||
"""Converts (lists of) Boolean values or strings into a multi-valued array.
|
||||
|
||||
The given values are interpreted and the axes are arranged as per KyuPy's convention.
|
||||
Use this function to convert strings into multi-valued arrays.
|
||||
"""
|
||||
mva = np.array(interpret(a), dtype=np.uint8)
|
||||
if mva.ndim < 2: return mva
|
||||
if mva.shape[-2] > 1: return mva.swapaxes(-1, -2)
|
||||
@@ -122,24 +110,13 @@ def mvarray(*a):
|
||||
|
||||
|
||||
def mv_str(mva, delim='\n'):
|
||||
"""Renders a given multi-valued array into a string.
|
||||
"""
|
||||
sa = np.choose(mva, np.array([*'0X-1PRFN'], dtype=np.unicode_))
|
||||
if mva.ndim == 1: return ''.join(sa)
|
||||
return delim.join([''.join(c) for c in sa.swapaxes(-1,-2)])
|
||||
|
||||
|
||||
def mv_to_bp(mva):
|
||||
if mva.ndim == 1: mva = mva[..., np.newaxis]
|
||||
return np.packbits(unpackbits(mva)[...,:3], axis=-2, bitorder='little').swapaxes(-1,-2)
|
||||
|
||||
|
||||
def bparray(*a):
|
||||
return mv_to_bp(mvarray(*a))
|
||||
|
||||
|
||||
def bp_to_mv(bpa):
|
||||
return packbits(np.unpackbits(bpa, axis=-1, bitorder='little').swapaxes(-1,-2))
|
||||
|
||||
|
||||
def _mv_not(out, inp):
|
||||
np.bitwise_xor(inp, 0b11, out=out) # this also exchanges UNASSIGNED <-> UNKNOWN
|
||||
np.putmask(out, (inp == UNKNOWN), UNKNOWN) # restore UNKNOWN
|
||||
@@ -148,13 +125,10 @@ def _mv_not(out, inp):
|
||||
def mv_not(x1 : np.ndarray, out=None):
|
||||
"""A multi-valued NOT operator.
|
||||
|
||||
:param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
|
||||
:param out: Optionally an :py:class:`MVArray` as storage destination. If None, a new :py:class:`MVArray`
|
||||
is returned.
|
||||
:return: An :py:class:`MVArray` with the result.
|
||||
:param x1: A multi-valued array.
|
||||
:param out: An optional storage destination. If None, a new multi-valued array is returned.
|
||||
:return: A multi-valued array with the result.
|
||||
"""
|
||||
#m = mv_getm(x1)
|
||||
#x1 = mv_cast(x1, m=m)[0]
|
||||
out = out or np.empty(x1.shape, dtype=np.uint8)
|
||||
_mv_not(out, x1)
|
||||
return out
|
||||
@@ -176,14 +150,11 @@ def _mv_or(out, *ins):
|
||||
def mv_or(x1, x2, out=None):
|
||||
"""A multi-valued OR operator.
|
||||
|
||||
:param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
|
||||
:param x2: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
|
||||
:param out: Optionally an :py:class:`MVArray` as storage destination. If None, a new :py:class:`MVArray`
|
||||
is returned.
|
||||
:return: An :py:class:`MVArray` with the result.
|
||||
:param x1: A multi-valued array.
|
||||
:param x2: A multi-valued array.
|
||||
:param out: An optional storage destination. If None, a new multi-valued array is returned.
|
||||
:return: A multi-valued array with the result.
|
||||
"""
|
||||
#m = mv_getm(x1, x2)
|
||||
#x1, x2 = mv_cast(x1, x2, m=m)
|
||||
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
|
||||
_mv_or(out, x1, x2)
|
||||
return out
|
||||
@@ -206,14 +177,11 @@ def _mv_and(out, *ins):
|
||||
def mv_and(x1, x2, out=None):
|
||||
"""A multi-valued AND operator.
|
||||
|
||||
:param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
|
||||
:param x2: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
|
||||
:param out: Optionally an :py:class:`MVArray` as storage destination. If None, a new :py:class:`MVArray`
|
||||
is returned.
|
||||
:return: An :py:class:`MVArray` with the result.
|
||||
:param x1: A multi-valued array.
|
||||
:param x2: A multi-valued array.
|
||||
:param out: An optional storage destination. If None, a new multi-valued array is returned.
|
||||
:return: A multi-valued array with the result.
|
||||
"""
|
||||
#m = mv_getm(x1, x2)
|
||||
#x1, x2 = mv_cast(x1, x2, m=m)
|
||||
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
|
||||
_mv_and(out, x1, x2)
|
||||
return out
|
||||
@@ -233,24 +201,28 @@ def _mv_xor(out, *ins):
|
||||
def mv_xor(x1, x2, out=None):
|
||||
"""A multi-valued XOR operator.
|
||||
|
||||
:param x1: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
|
||||
:param x2: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
|
||||
:param out: Optionally an :py:class:`MVArray` as storage destination. If None, a new :py:class:`MVArray`
|
||||
is returned.
|
||||
:return: An :py:class:`MVArray` with the result.
|
||||
:param x1: A multi-valued array.
|
||||
:param x2: A multi-valued array.
|
||||
:param out: An optional storage destination. If None, a new multi-valued array is returned.
|
||||
:return: A multi-valued array with the result.
|
||||
"""
|
||||
#m = mv_getm(x1, x2)
|
||||
#x1, x2 = mv_cast(x1, x2, m=m)
|
||||
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
|
||||
_mv_xor(out, x1, x2)
|
||||
return out
|
||||
|
||||
|
||||
def mv_latch(d, t, q_prev, out=None):
|
||||
"""A latch that is transparent if `t` is high. `q_prev` has to be the output value from the previous clock cycle.
|
||||
"""A multi-valued latch operator.
|
||||
|
||||
A latch outputs ``d`` when transparent (``t`` is high).
|
||||
It outputs ``q_prev`` when in latched state (``t`` is low).
|
||||
|
||||
:param d: A multi-valued array for the data input.
|
||||
:param t: A multi-valued array for the control input.
|
||||
:param q_prev: A multi-valued array with the output value of this latch from the previous clock cycle.
|
||||
:param out: An optional storage destination. If None, a new multi-valued array is returned.
|
||||
:return: A multi-valued array for the latch output ``q``.
|
||||
"""
|
||||
#m = mv_getm(d, t, q_prev)
|
||||
#d, t, q_prev = mv_cast(d, t, q_prev, m=m)
|
||||
out = out or np.empty(np.broadcast(d, t, q_prev).shape, dtype=np.uint8)
|
||||
out[...] = t & d & 0b011
|
||||
out[...] |= ~t & 0b010 & (q_prev << 1)
|
||||
@@ -268,16 +240,11 @@ def mv_transition(init, final, out=None):
|
||||
Pulses in the input data are ignored. If any of the inputs are ``UNKNOWN``, the result is ``UNKNOWN``.
|
||||
If both inputs are ``UNASSIGNED``, the result is ``UNASSIGNED``.
|
||||
|
||||
:param init: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
|
||||
:param final: An :py:class:`MVArray` or data the :py:class:`MVArray` constructor accepts.
|
||||
:param out: Optionally an :py:class:`MVArray` as storage destination. If None, a new :py:class:`MVArray`
|
||||
is returned.
|
||||
:return: An :py:class:`MVArray` with the result.
|
||||
:param init: A multi-valued array.
|
||||
:param final: A multi-valued array.
|
||||
:param out: An optional storage destination. If None, a new multi-valued array is returned.
|
||||
:return: A multi-valued array with the result.
|
||||
"""
|
||||
#m = mv_getm(init, final)
|
||||
#init, final = mv_cast(init, final, m=m)
|
||||
#init = init.data
|
||||
#final = final.data
|
||||
out = out or np.empty(np.broadcast(init, final).shape, dtype=np.uint8)
|
||||
out[...] = (init & 0b010) | (final & 0b001)
|
||||
out[...] |= ((out << 1) ^ (out << 2)) & 0b100
|
||||
@@ -288,6 +255,28 @@ def mv_transition(init, final, out=None):
|
||||
return out
|
||||
|
||||
|
||||
def mv_to_bp(mva):
|
||||
"""Converts a multi-valued array into a bit-parallel array.
|
||||
"""
|
||||
if mva.ndim == 1: mva = mva[..., np.newaxis]
|
||||
return np.packbits(unpackbits(mva)[...,:3], axis=-2, bitorder='little').swapaxes(-1,-2)
|
||||
|
||||
|
||||
def bparray(*a):
|
||||
"""Converts (lists of) Boolean values or strings into a bit-parallel array.
|
||||
|
||||
The given values are interpreted and the axes are arranged as per KyuPy's convention.
|
||||
Use this function to convert strings into bit-parallel arrays.
|
||||
"""
|
||||
return mv_to_bp(mvarray(*a))
|
||||
|
||||
|
||||
def bp_to_mv(bpa):
|
||||
"""Converts a bit-parallel array into a multi-valued array.
|
||||
"""
|
||||
return packbits(np.unpackbits(bpa, axis=-1, bitorder='little').swapaxes(-1,-2))
|
||||
|
||||
|
||||
def bp_buf(out, inp):
|
||||
unknown = (inp[..., 0, :] ^ inp[..., 1, :]) & ~inp[..., 2, :]
|
||||
out[..., 0, :] = inp[..., 0, :] | unknown
|
||||
@@ -356,3 +345,37 @@ def bp_latch(out, d, t, q_prev):
|
||||
out[..., 1, :] &= ~any_unknown
|
||||
out[..., 2, :] &= ~any_unknown
|
||||
return out
|
||||
|
||||
|
||||
_bit_in_lut = np.array([2 ** x for x in range(7, -1, -1)], dtype='uint8')
|
||||
|
||||
|
||||
@numba.njit
|
||||
def bit_in(a, pos):
|
||||
return a[pos >> 3] & _bit_in_lut[pos & 7]
|
||||
|
||||
|
||||
def unpackbits(a : np.ndarray):
|
||||
"""Unpacks the bits of given ndarray ``a``.
|
||||
|
||||
Similar to ``np.unpackbits``, but accepts any dtype, preserves the shape of ``a`` and
|
||||
adds a new last axis with the bits of each item. Bits are in 'little'-order, i.e.,
|
||||
a[...,0] is the least significant bit of each item.
|
||||
"""
|
||||
return np.unpackbits(a.view(np.uint8), bitorder='little').reshape(*a.shape, 8*a.itemsize)
|
||||
|
||||
|
||||
def packbits(a, dtype=np.uint8):
|
||||
"""Packs the values of a boolean-valued array ``a`` along its last axis into bits.
|
||||
|
||||
Similar to ``np.packbits``, but returns an array of given dtype and the shape of ``a`` with the last axis removed.
|
||||
The last axis of `a` is truncated or padded according to the bit-width of the given dtype.
|
||||
Signed integer datatypes are padded with the most significant bit, all others are padded with `0`.
|
||||
"""
|
||||
dtype = np.dtype(dtype)
|
||||
bits = 8 * dtype.itemsize
|
||||
a = a[...,:bits]
|
||||
if a.shape[-1] < bits:
|
||||
p = [(0,0)]*(len(a.shape)-1) + [(0, bits-a.shape[-1])]
|
||||
a = np.pad(a, p, 'edge') if dtype.name[0] == 'i' else np.pad(a, p, 'constant', constant_values=0)
|
||||
return np.packbits(a, bitorder='little').view(dtype).reshape(a.shape[:-1])
|
||||
+11
-9
@@ -1,10 +1,10 @@
|
||||
"""A simple and incomplete parser for the Standard Delay Format (SDF).
|
||||
|
||||
The main purpose of this parser is to extract pin-to-pin delay and interconnect delay information from SDF files.
|
||||
Sophisticated timing specifications (timing checks, conditional delays, etc.) are currently not supported.
|
||||
This parser extracts pin-to-pin delay and interconnect delay information from SDF files.
|
||||
Sophisticated timing specifications (timing checks, conditional delays, etc.) are ignored.
|
||||
|
||||
The functions :py:func:`load` and :py:func:`read` return an intermediate representation (:class:`DelayFile` object).
|
||||
Call :py:func:`DelayFile.annotation` to match the intermediate representation to a given circuit.
|
||||
Call :py:func:`DelayFile.iopaths` to match the intermediate representation to a given circuit.
|
||||
|
||||
"""
|
||||
|
||||
@@ -38,12 +38,12 @@ class DelayFile:
|
||||
def iopaths(self, circuit:Circuit, tlib=TechLib()):
|
||||
"""Constructs an ndarray containing all IOPATH delays.
|
||||
|
||||
All IOPATH delays for a node `n` are annotated to the line connected to the input pin specified in the IOPATH.
|
||||
All IOPATH delays for a node ``n`` are annotated to the line connected to the input pin specified in the IOPATH.
|
||||
|
||||
Axis 0: dataset (usually 3 datasets per SDF-file)
|
||||
Axis 1: line index (e.g. `n.ins[0]`, `n.ins[1]`)
|
||||
Axis 2: polarity of the transition at the IOPATH-input (e.g. at `n.ins[0]` or `n.ins[1]`), 0='rising/posedge', 1='falling/negedge'
|
||||
Axis 3: polarity of the transition at the IOPATH-output (at `n.outs[0]`), 0='rising/posedge', 1='falling/negedge'
|
||||
* Axis 0: dataset (usually 3 datasets per SDF-file)
|
||||
* Axis 1: line index (e.g. ``n.ins[0]``, ``n.ins[1]``)
|
||||
* Axis 2: polarity of the transition at the IOPATH-input (e.g. at ``n.ins[0]`` or ``n.ins[1]``), 0='rising/posedge', 1='falling/negedge'
|
||||
* Axis 3: polarity of the transition at the IOPATH-output (at ``n.outs[0]``), 0='rising/posedge', 1='falling/negedge'
|
||||
"""
|
||||
|
||||
def find_cell(name:str):
|
||||
@@ -72,6 +72,8 @@ class DelayFile:
|
||||
def annotation(self, circuit:Circuit, tlib=TechLib(), dataset=1, interconnect=True, ffdelays=True):
|
||||
"""Constructs an 3-dimensional ndarray with timing data for each line in ``circuit``.
|
||||
|
||||
DEPRECATED
|
||||
|
||||
An IOPATH delay for a node is annotated to the line connected to the input pin specified in the IOPATH.
|
||||
|
||||
Currently, only ABSOLUTE IOPATH and INTERCONNECT delays are supported.
|
||||
@@ -266,6 +268,6 @@ def parse(text):
|
||||
def load(file):
|
||||
"""Parses the contents of ``file`` and returns a :class:`DelayFile` object.
|
||||
|
||||
The given file may be gzip compressed.
|
||||
Files with `.gz`-suffix are decompressed on-the-fly.
|
||||
"""
|
||||
return parse(readtext(file))
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
"""A simple and incomplete parser for the Standard Test Interface Language (STIL).
|
||||
|
||||
The main purpose of this parser is to load scan pattern sets from STIL files.
|
||||
It supports only a very limited subset of STIL.
|
||||
It supports only a subset of STIL.
|
||||
|
||||
The functions :py:func:`load` and :py:func:`read` return an intermediate representation (:class:`StilFile` object).
|
||||
Call :py:func:`StilFile.tests`, :py:func:`StilFile.tests_loc`, or :py:func:`StilFile.responses` to
|
||||
@@ -248,6 +248,6 @@ def parse(text):
|
||||
def load(file):
|
||||
"""Parses the contents of ``file`` and returns a :class:`StilFile` object.
|
||||
|
||||
The given file may be gzip compressed.
|
||||
Files with `.gz`-suffix are decompressed on-the-fly.
|
||||
"""
|
||||
return parse(readtext(file))
|
||||
@@ -1,7 +1,7 @@
|
||||
"""A simple and incomplete parser for Verilog files.
|
||||
|
||||
The main purpose of this parser is to load synthesized, non-hierarchical (flat) gate-level netlists.
|
||||
It supports only a very limited subset of Verilog.
|
||||
It supports only a subset of Verilog.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -80,7 +80,7 @@ def test_b14(mydir):
|
||||
def test_gates(mydir):
|
||||
c = verilog.load(mydir / 'gates.v')
|
||||
df = sdf.load(mydir / 'gates.sdf')
|
||||
lt = df.annotation(c, dataset=1)
|
||||
lt = df.iopaths(c)[1]
|
||||
nand_a = c.cells['nandgate'].ins[0]
|
||||
nand_b = c.cells['nandgate'].ins[1]
|
||||
and_a = c.cells['andgate'].ins[0]
|
||||
|
||||
Reference in new issue
Block a user