Compare commits

..

2 Commits
devel ... main

Author SHA1 Message Date
Nosaka Naoya 3193df726f need 3.12 for generic types 7 months ago
Nosaka Naoya d247115744 for release 0.0.6 7 months ago
  1. 4
      docs/conf.py
  2. 6
      pyproject.toml
  3. 48
      src/kyupy/__init__.py
  4. 64
      src/kyupy/atalanta.py
  5. 32
      src/kyupy/bench.py
  6. 91
      src/kyupy/circuit.py
  7. 63
      src/kyupy/logic.py
  8. 64
      src/kyupy/logic_sim.py
  9. 17
      src/kyupy/sdf.py
  10. 50
      src/kyupy/sim.py
  11. 12
      src/kyupy/stil.py
  12. 233
      src/kyupy/techlib.py
  13. 218
      src/kyupy/vcd.py
  14. 15
      src/kyupy/verilog.py
  15. 2
      src/kyupy/wave_sim.py
  16. 29
      tests/test_atalanta.py
  17. 6
      tests/test_circuit.py
  18. 103
      tests/test_logic_sim.py
  19. 2
      tests/test_stil.py
  20. 8
      tests/test_verilog.py
  21. 8
      tests/test_wave_sim.py

4
docs/conf.py

@ -20,11 +20,11 @@ sys.path.insert(0, os.path.abspath('../src')) @@ -20,11 +20,11 @@ sys.path.insert(0, os.path.abspath('../src'))
# -- Project information -----------------------------------------------------
project = 'KyuPy'
copyright = '2020-2026, Stefan Holst'
copyright = '2020-2025, Stefan Holst'
author = 'Stefan Holst'
# The full version, including alpha/beta/rc tags
release = '0.0.7'
release = '0.0.6'
# -- General configuration ---------------------------------------------------

6
pyproject.toml

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
[project]
name = "kyupy"
version = "0.0.7"
version = "0.0.6"
authors = [
{ name="Stefan Holst", email="mail@s-holst.de" },
]
@ -33,10 +33,8 @@ build-backend = "hatchling.build" @@ -33,10 +33,8 @@ build-backend = "hatchling.build"
[dependency-groups]
dev = [
"myst-parser>=4.0.1",
"pytest>=9.0.1",
"sphinx>=8.1.3",
"sphinx-rtd-theme>=3.0.2",
"pytest>=9.0.1",
"ipython>=9.14.1",
"jupyter>=1.1.1",
]

48
src/kyupy/__init__.py

@ -12,8 +12,6 @@ import gzip @@ -12,8 +12,6 @@ import gzip
import numpy as np
type NestedNumericList = list['int|NestedNumericList']
type NestedStrIntDict = dict[str,'int|NestedStrIntDict']
_pop_count_lut = np.asarray([bin(x).count('1') for x in range(256)])
@ -113,9 +111,9 @@ class Timer: @@ -113,9 +111,9 @@ class Timer:
class Timers:
def __init__(self, t={}): self.timers: dict[str, Timer] = defaultdict(Timer) | t
def __init__(self, t={}): self.timers = defaultdict(Timer) | t
def __getitem__(self, name): return self.timers[name]
def __repr__(self): return '{' + ', '.join([f'{k}: {v:.2f}' for k, v in self.dict().items()]) + '}'
def __repr__(self): return '{' + ', '.join([f'{k}: {v}' for k, v in self.timers.items()]) + '}'
def __add__(self, t):
tmr = Timers(self.timers)
for k, v in t.timers.items(): tmr.timers[k] += v
@ -123,7 +121,7 @@ class Timers: @@ -123,7 +121,7 @@ class Timers:
def sum(self):
return sum([v.s for v in self.timers.values()])
def dict(self):
return dict(sorted([(k, v.s) for k, v in self.timers.items()], key=lambda item: -item[1]))
return dict([(k, v.s) for k, v in self.timers.items()])
class Log:
@ -200,35 +198,25 @@ class Log: @@ -200,35 +198,25 @@ class Log:
"""Log an error message."""
self.log('E', message)
def progress(self):
class Progress:
def __init__(self, l): self.l = l
def __enter__(self):
self.start_time = time.perf_counter()
self.lastlog_time = self.start_time
self.log_interval = 5
return self
def update(self, completion: float, msg: str = ''):
assert completion > 0 and completion <= 1
current_time = time.perf_counter()
if current_time > self.lastlog_time + self.log_interval:
elapsed_time = current_time - self.start_time
total_time = elapsed_time / completion
rem_time = total_time - elapsed_time
self.l.log(':', f'{msg} - {completion*100:.0f}% done {hr_time(elapsed_time)} elapsed {hr_time(rem_time)} remaining')
self.log_interval = min(600, int(self.log_interval*1.5))
self.lastlog_time = current_time
def __exit__(self, *args): pass
return Progress(self)
def range(self, *args):
"""A generator that operates just like the ``range()`` built-in, and also occasionally logs the progress
and compute time estimates."""
elems = len(range(*args))
with self.progress() as p:
for elem, i in enumerate(range(*args)):
yield i
p.update((elem + 1) / elems)
start_time = time.perf_counter()
lastlog_time = start_time
log_interval = 5
for elem, i in enumerate(range(*args)):
yield i
current_time = time.perf_counter()
if current_time > lastlog_time + log_interval:
done = (elem + 1) / elems
elapsed_time = current_time - start_time
total_time = elapsed_time / done
rem_time = total_time - elapsed_time
self.log(
':', f'{done*100:.0f}% done {hr_time(elapsed_time)} elapsed {hr_time(rem_time)} remaining')
log_interval = min(600, int(log_interval*1.5))
lastlog_time = current_time
log = Log()

64
src/kyupy/atalanta.py

@ -1,64 +0,0 @@ @@ -1,64 +0,0 @@
"""A parser and dumper for Atalanta's plain-text test pattern format (*.test).
A *.test file contains one test pattern per line in the form ``<index>: <bits>``,
where ``<bits>`` is a string of ``'0'`` and ``'1'`` characters giving the values of
the primary inputs (in circuit order). Lines starting with ``'*'`` are comments.
This parser also accepts files without ``<index>:`` and patterns elements in 8-valued
logic such as ``'R'``, ``'F'``, ``'X'``.
"""
import re
import numpy as np
from . import readtext, logic
from .circuit import Circuit
_pattern_re = re.compile(r'^\s*(?:\d+:\s*)?([01HLRFX-]+)\s*$')
class TestFile:
"""Intermediate representation of a *.test pattern file.
:param text: The contents of a *.test file.
"""
def __init__(self, text: str):
patterns = []
for line in text.splitlines():
if (m := _pattern_re.match(line)) is not None:
patterns.append(logic.mvarray(m.group(1)))
self.patterns = np.stack(patterns, axis=-1) if patterns \
else np.zeros((0, 0), dtype=np.uint8)
"""The parsed patterns as a mvarray (see :py:mod:`~kyupy.logic`).
The second-to-last axis goes along primary inputs, the last axis goes along patterns.
"""
def tests(self, circuit: Circuit):
"""Assembles and returns the test pattern set for the given circuit.
:param circuit: The circuit to assemble the patterns for. The patterns will follow the
:py:attr:`~kyupy.circuit.Circuit.io_nodes` ordering of the circuit.
:return: A logic array (see :py:mod:`~kyupy.logic`). The values for primary inputs are
filled, all other values are left unassigned.
"""
pi_locs = [i for i, n in enumerate(circuit.io_nodes) if len(n.ins) == 0]
tests = np.full((len(circuit.io_nodes), self.patterns.shape[-1]), logic.UNASSIGNED, dtype=np.uint8)
tests[pi_locs] = self.patterns
return tests
def parse(text) -> TestFile:
"""Parses the given ``text`` and returns a :class:`TestFile` object."""
return TestFile(text)
def load(file) -> TestFile:
"""Parses the contents of ``file`` and returns a :class:`TestFile` object.
Files with `.gz`-suffix are decompressed on-the-fly.
"""
return parse(readtext(file))

32
src/kyupy/bench.py

@ -5,16 +5,12 @@ Historically it was first used in the @@ -5,16 +5,12 @@ Historically it was first used in the
`ISCAS89 benchmark set <https://people.engr.ncsu.edu/brglez/CBL/benchmarks/ISCAS89/>`_.
Besides loading these benchmarks, this module is also useful for easily constructing simple circuits:
``c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')``.
This parser automatically translates gate to simulation primitives supported by kyupy.
In most cases this means just adding the number of inputs to the name: e.g. NAND -> NAND2, OR -> OR4, INV -> INV1.
Since simulators only support at most four-input-gates, gates with more than four inputs are implemented as tree.
"""
from lark import Lark, Transformer
from .circuit import Circuit, Node, Line
from . import readtext, batchrange, log
from . import readtext, batchrange
def treeify(l, max_degree=4):
if len(l) <= max_degree: return l
@ -26,32 +22,12 @@ class BenchTransformer(Transformer): @@ -26,32 +22,12 @@ class BenchTransformer(Transformer):
def __init__(self, name):
super().__init__()
self.c = Circuit(name)
self.inputs = set()
self.outputs = set()
def start(self, _): return self.c
def parameters(self, args): return [self.c.get_or_add_fork(str(name)) for name in args if name is not None]
def input(self, args):
for input in args[0]:
if input in self.outputs:
new_input = self.c.get_or_add_fork(input.name + '~i')
Line(self.c, new_input, input)
log.warn(f'input-output passthrough, renaming input: {input.name} -> {new_input.name}')
input = new_input
self.inputs.add(input)
self.c.io_nodes.append(input)
def output(self, args):
for output in args[0]:
if output in self.inputs:
new_output = self.c.get_or_add_fork(output.name + '~o')
Line(self.c, output, new_output)
log.warn(f'input-output passthrough, renaming output: {output.name} -> {new_output.name}')
output = new_output
self.outputs.add(output)
self.c.io_nodes.append(output)
def interface(self, args): self.c.io_nodes.extend(args[0])
def _cell_tree_inner(self, name, kind, inner_kind, drivers):
cell = Node(self.c, name, f'{kind}{len(drivers)}')
@ -89,8 +65,8 @@ class BenchTransformer(Transformer): @@ -89,8 +65,8 @@ class BenchTransformer(Transformer):
GRAMMAR = r"""
start: (statement)*
statement: input | output | assignment
input: ("INPUT" | "input") parameters
output: ("OUTPUT" | "output") parameters
input: ("INPUT" | "input") parameters -> interface
output: ("OUTPUT" | "output") parameters -> interface
assignment: NAME "=" NAME parameters
parameters: "(" [ NAME ( "," NAME )* ] ")"
NAME: /[-_a-z0-9]+/i

91
src/kyupy/circuit.py

@ -14,10 +14,10 @@ from __future__ import annotations @@ -14,10 +14,10 @@ from __future__ import annotations
from collections import deque, defaultdict
import re
from typing import Union, Any
import numpy as np
from . import NestedNumericList, NestedStrIntDict
class GrowingList[T](list[T]):
def __setitem__(self, index, value):
@ -280,19 +280,20 @@ class Circuit: @@ -280,19 +280,20 @@ class Circuit:
Use the :class:`Node` constructor and :py:attr:`Node.remove()` to add and remove nodes.
"""
def s_nodes(self, tlib: 'TechLib') -> list: # type: ignore
@property
def s_nodes(self):
"""A list of all primary I/Os as well as all flip-flops and latches in the circuit (in that order).
The s_nodes list defines the order of all ports and all sequential elements in the circuit.
This list is constructed on-the-fly. If used in some inner toop, consider caching the list for better performance.
"""
return list(self.io_nodes) + [n for n in self.nodes if tlib.is_dff(n.kind)] + [n for n in self.nodes if tlib.is_latch(n.kind)]
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 a list of indices of primary I/Os that start with given name 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 list with a single integer is returned.
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]`).
@ -302,16 +303,16 @@ class Circuit: @@ -302,16 +303,16 @@ class Circuit:
"""
return self._locs(prefix, list(self.io_nodes))
def s_locs(self, prefix, tlib: 'TechLib'): # type: ignore
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` list.
It works the same as :py:attr:`io_locs`. See there for more details.
"""
return self._locs(prefix, self.s_nodes(tlib))
return self._locs(prefix, self.s_nodes)
def _locs(self, prefix, nodes:list[Node]) -> NestedNumericList: # can return list[list[...]]
d_top: NestedStrIntDict = dict()
def _locs(self, prefix, nodes:list[Node]) -> Node|list[Any]: # can return list[list[...]]
d_top = dict()
for i, n in enumerate(nodes):
if m := re.match(fr'({re.escape(prefix)}.*?)((?:[\d_\[\]])*$)', n.name):
path = [m[1]] + [int(v) for v in re.split(r'[_\[\]]+', m[2]) if len(v) > 0]
@ -319,16 +320,16 @@ class Circuit: @@ -319,16 +320,16 @@ class Circuit:
for j in path[:-1]:
d[j] = d.get(j, dict())
d = d[j]
assert isinstance(d, dict)
d[path[-1]] = i
# sort recursively for multi-dimensional lists.
def sorted_values(d) -> NestedNumericList: return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d
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 and isinstance(l[0], list): l = l[0]
return l
while isinstance(l, list) and len(l) == 1: l = l[0]
return l #None if isinstance(l, list) and len(l) == 0 else l
def stats(self, tlib: 'TechLib'): # type: ignore
@property
def stats(self):
"""A dictionary with the counts of all different elements in the circuit.
The dictionary contains the number of all different kinds of nodes, the number
@ -346,9 +347,9 @@ class Circuit: @@ -346,9 +347,9 @@ class Circuit:
stats['__line__'] = len(self.lines)
for n in self.cells.values():
stats[n.kind] += 1
if tlib.is_dff(n.kind): stats['__dff__'] += 1
elif tlib.is_latch(n.kind): stats['__latch__'] += 1
elif 'put' not in n.kind.lower(): stats['__comb__'] += 1 # don't count input or output towards comb
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)
@ -447,7 +448,7 @@ class Circuit: @@ -447,7 +448,7 @@ class Circuit:
node_map[n] = Node(self, f'{node.name}~{n.name}', n.kind)
elif len(n.outs) > 0 and len(n.ins) > 0: # output is also read by impl. circuit, need to add a fork.
node_map[n] = Node(self, f'{node.name}~{n.name}')
elif len(n.ins) == 0 and len(n.outs) != 1: # input is read by multiple nodes (or no nodes), need to add fork.
elif len(n.ins) == 0 and len(n.outs) > 1: # input is read by multiple nodes, need to add fork.
node_map[n] = Node(self, f'{node.name}~{n.name}')
for l in impl.lines: # add all internal lines to main circuit
if l.reader in node_map and l.driver in node_map:
@ -580,23 +581,24 @@ class Circuit: @@ -580,23 +581,24 @@ class Circuit:
if line is not None:
yield line
def reversed_topological_order(self, tlib: 'TechLib'): # type: ignore
def reversed_topological_order(self):
"""Generator function to iterate over all nodes in reversed topological order.
Nodes without output lines and sequential nodes (flip-flops, latches) 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 tlib.is_dff(n.kind) or tlib.is_latch(n.kind))
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 not tlib.is_dff(pred.kind) and not tlib.is_latch(pred.kind):
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
def fanin(self, origin_nodes, tlib: 'TechLib'): # type: ignore
def fanin(self, origin_nodes):
"""Generator function to iterate over the fan-in cone of a given list of origin nodes.
Nodes are yielded in reversed topological order.
@ -604,34 +606,31 @@ class Circuit: @@ -604,34 +606,31 @@ class Circuit:
marks = [False] * len(self.nodes)
for n in origin_nodes:
marks[n] = True
for n in self.reversed_topological_order(tlib):
for n in self.reversed_topological_order():
if not marks[n]:
for line in n.outs.without_nones():
marks[n] |= marks[line.reader]
for line in n.outs:
if line is not None:
marks[n] |= marks[line.reader]
if marks[n]:
yield n
def fanout(self, origin_nodes: list[Node], node_filter = lambda n: 'dff' not in n.name.lower()):
def fanout(self, origin_nodes):
"""Generator function to iterate over the fan-out cone of a given list of origin nodes.
origin_nodes are yielded first, followed by nodes driven by them in a breadth-first manner.
The search stops at nodes for which node_filter returns False.
Only origin_nodes and nodes for which node_filter returned True are yielded.
By default, search stops at flip-flops.
Nodes are yielded in topological order.
"""
queue = deque(origin_nodes)
yielded = set()
while len(queue) > 0:
n = queue.popleft()
for line in n.outs.without_nones():
succ = line.reader
if succ not in yielded and node_filter(succ):
yielded.add(succ)
queue.append(succ)
yield n
marks = [False] * len(self.nodes)
for n in origin_nodes:
marks[n] = True
for n in self.topological_order():
if not marks[n]:
for line in n.ins.without_nones():
marks[n] |= marks[line.driver]
if marks[n]:
yield n
def fanout_free_regions(self, tlib: 'TechLib'): # type: ignore
for stem in self.reversed_topological_order(tlib):
def fanout_free_regions(self):
for stem in self.reversed_topological_order():
if len(stem.outs) == 1 and 'dff' not in stem.kind.lower(): continue
region = []
if 'dff' in stem.kind.lower():
@ -651,11 +650,11 @@ class Circuit: @@ -651,11 +650,11 @@ class Circuit:
region.append(n)
yield stem, region
def dot(self, tlib: 'TechLib', format='svg', graph_attr={}, line_labels={}):
def dot(self, format='svg', graph_attr={}, line_labels={}):
from graphviz import Digraph
dot = Digraph(format=format, graph_attr={'rankdir': 'LR', 'splines': 'true', 'size': '10', 'ranksep': '0.1'} | graph_attr)
s_dict = dict((n, i) for i, n in enumerate(self.s_nodes(tlib)))
s_dict = dict((n, i) for i, n in enumerate(self.s_nodes))
node_level = np.zeros(len(self.nodes), dtype=np.uint32)
level_nodes = defaultdict(list)
for n, lv in self.topological_order_with_level():
@ -678,7 +677,7 @@ class Circuit: @@ -678,7 +677,7 @@ class Circuit:
reader = f'{l.reader.index}:i{l.reader_pin}' if len(l.reader.ins)>1 else f'{l.reader.index}'
label = str(line_labels.get(l, l.index))
if node_level[l.driver] == node_level[l.reader]:
dot.node(f'_{l.index}_')
s.node(f'_{l.index}_')
dot.edge(driver, f'_{l.index}_', style='dotted', label=label)
dot.edge(f'_{l.index}_', reader, style='dotted', label=label)
elif node_level[l.driver] > node_level[l.reader]:

63
src/kyupy/logic.py

@ -49,7 +49,7 @@ from collections.abc import Iterable @@ -49,7 +49,7 @@ from collections.abc import Iterable
import numpy as np
from numpy.typing import DTypeLike
from . import numba, hr_bytes, NestedNumericList
from . import numba, hr_bytes
ZERO = 0b000
@ -81,30 +81,7 @@ on a signal. ``'N'``, ``'n'``, and ``'v'`` are interpreted as ``NPULSE``. @@ -81,30 +81,7 @@ on a signal. ``'N'``, ``'n'``, and ``'v'`` are interpreted as ``NPULSE``.
"""
# 0 X - 1 P R F N
_mv_xor = np.array([[0,1,1,3,4,5,6,7], # 0
[1,1,1,1,1,1,1,1], # X
[1,1,1,1,1,1,1,1], # -
[3,1,1,0,7,6,5,4], # 1
[4,1,1,7,4,5,6,7], # P
[5,1,1,6,5,4,7,6], # R
[6,1,1,5,6,7,4,5], # F
[7,1,1,4,7,6,5,4], # N
], dtype=np.uint8)
# 0 X - 1 P R F N
_mv_merge = np.array([[0,0,0,1,1,1,1,1], # 0
[0,1,1,3,4,5,6,7], # X
[0,1,2,3,4,5,6,7], # -
[1,3,3,3,1,1,1,1], # 1
[1,4,4,1,4,1,1,1], # P
[1,5,5,1,1,5,1,1], # R
[1,6,6,1,1,1,6,1], # F
[1,7,7,1,1,1,1,7], # N
], dtype=np.uint8)
def interpret(value) -> int|NestedNumericList:
def interpret(value):
"""Converts characters, strings, and lists of them to lists of logic constants defined above.
:param value: A character (string of length 1), Boolean, Integer, None, or Iterable.
@ -123,12 +100,11 @@ def interpret(value) -> int|NestedNumericList: @@ -123,12 +100,11 @@ def interpret(value) -> int|NestedNumericList:
return UNKNOWN
def mvarray(*a) -> np.ndarray[tuple[int]|tuple[int,int],np.dtype[np.uint8]]:
def mvarray(*a):
"""Converts (lists of) Boolean values or strings into a multi-valued array.
Iterable over characters, Booleans, Integers (0,1), None are interpreted as a single pattern.
Multi-character strings are Iterables over characters and are interpreted as a single pattern.
Lists of said patterns form a pattern set as ndarray with the axes are arranged as per KyuPy's convention.
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
@ -215,26 +191,19 @@ def mv_and(x1, x2, out=None): @@ -215,26 +191,19 @@ def mv_and(x1, x2, out=None):
return out
def mv_xor(x1, x2, out=None):
"""A multi-valued XOR operator.
: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.
"""
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
out[...] = _mv_xor[x1, x2]
return out
def _mv_xor(out, *ins):
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED)
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
out[...] = ZERO
for inp in ins:
np.bitwise_xor(out, inp & 0b011, out=out)
np.bitwise_or(out, inp & 0b100, out=out)
np.putmask(out, any_unknown, UNKNOWN)
def mv_merge(x1, x2, out=None):
"""A multi-valued merge operator.
Merging identical values ``a`` and ``a`` yields ``a``.
Merging ``X`` and ``-`` yields ``X``.
Merging any value ``a`` with ``X`` or ``-`` yields ``a``.
Merging any other values ``a != b`` yields ``X``.
def mv_xor(x1, x2, out=None):
"""A multi-valued XOR operator.
:param x1: A multi-valued array.
:param x2: A multi-valued array.
@ -242,7 +211,7 @@ def mv_merge(x1, x2, out=None): @@ -242,7 +211,7 @@ def mv_merge(x1, x2, out=None):
:return: A multi-valued array with the result.
"""
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
out[...] = _mv_merge[x1, x2]
_mv_xor(out, x1, x2)
return out

64
src/kyupy/logic_sim.py

@ -1,24 +1,12 @@ @@ -1,24 +1,12 @@
"""High-throughput combinational logic simulators.
These simulators take batches of input assignments to a logic circuit and compute batches of output values.
Each batch is simulated in data-parallel fashion using packed numpy arrays (see bit-parallel (bp) array description in :py:mod:`~kyupy.logic`) for each signal in the circuit.
All simulations within a batch are mutually independent.
Inner simulation loops (that iterate over the topologically sorted circuit model) are just-in-time (JIT) compiled using numba.
JIT compiling triggered by the first simulation may take several seconds.
If numba is not available, inner loop is executed in the python interpreter and is significantly slower.
Circuits passed to these simulators must contain only cells defined in the :py:class:`~kyupy.techlib.KYUPY` cell library.
Other circuits must be mapped to that library first using :py:method:`~kyupy.circuit.resolve_tlib_cells()`.
Sequential circuits are supported with some caveats.
The simulation model is built by stripping out all flip-flops, clock, and asynchronous set/reset logic.
Values stored in the flip-flops become part of the input assignments and output values (pseudo-primary I/O).
Only the combinational portion is actually simulated.
Simulation results remain correct under the assumption that all flip-clops are clocked all the time and set/reset logic is passive.
"""A high-throughput combinational logic simulator.
The class :py:class:`~kyupy.logic_sim.LogicSim` performs parallel simulations of the combinational part of a circuit.
The logic operations are performed bit-parallel on packed numpy arrays (see bit-parallel (bp) array description in :py:mod:`~kyupy.logic`).
Simple sequential circuits can be simulated by repeated assignments and propagations.
However, this simulator ignores the clock network and simply assumes that all state-elements are clocked all the time.
"""
import math
import warnings
import numpy as np
@ -29,10 +17,6 @@ from .circuit import Circuit, Line @@ -29,10 +17,6 @@ from .circuit import Circuit, Line
class LogicSim(sim.SimOps):
"""A bit-parallel naïve combinational simulator for 2-, 4-, or 8-valued logic.
.. deprecated::
Use the specialized :py:class:`LogicSim2V`, :py:class:`LogicSim4V`, or :py:class:`LogicSim6V`
simulators instead.
:param circuit: The circuit to simulate.
:param sims: The number of parallel logic simulations to perform.
:param m: The arity of the logic, must be 2, 4, or 8.
@ -40,9 +24,6 @@ class LogicSim(sim.SimOps): @@ -40,9 +24,6 @@ class LogicSim(sim.SimOps):
:param strip_forks: If True, forks are not included in the simulation model to save memory and simulation time.
"""
def __init__(self, circuit: Circuit, sims: int = 8, m: int = 8, c_reuse: bool = False, strip_forks: bool = False):
warnings.warn(
'LogicSim is deprecated; use LogicSim2V, LogicSim4V, or LogicSim6V instead.',
DeprecationWarning, stacklevel=2)
assert m in [2, 4, 8]
super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks)
self.m = m
@ -51,7 +32,6 @@ class LogicSim(sim.SimOps): @@ -51,7 +32,6 @@ class LogicSim(sim.SimOps):
nbytes = cdiv(sims, 8)
self.c = np.zeros((self.c_len, self.mdim, nbytes), dtype=np.uint8)
self.c_dirty = np.full(self.c_len, 1, dtype=np.uint8)
self.s = np.zeros((2, self.s_len, 3, nbytes), dtype=np.uint8)
"""Logic values of the sequential elements (flip-flops) and ports.
@ -98,7 +78,7 @@ class LogicSim(sim.SimOps): @@ -98,7 +78,7 @@ class LogicSim(sim.SimOps):
t1 = self.c_locs[self.tmp2_idx]
if self.m == 2:
if inject_cb is None:
c_prop_2v_cpu(self.ops, self.c_locs, self.c, self.c_dirty, int(fault_line), fault_mask, int(fault_model))
c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
else:
for op, o0l, i0l, i1l, i2l, i3l in self.ops[:,:6]:
o0, i0, i1, i2, i3 = [self.c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
@ -357,19 +337,6 @@ class LogicSim2V(sim.SimOps): @@ -357,19 +337,6 @@ class LogicSim2V(sim.SimOps):
Storage locations are indirectly addressed.
Data for line `l` is in `self.c[self.c_locs[l]]`.
"""
self.c_dirty = np.full(self.c_len, 1, dtype=np.uint8)
"""Marker for logic values that have changed recently for use in incremental simulation.
A node n will be evaluated by `c_prop()` if and only if at least one of its inputs is marked dirty (`c_dirty[c_locs[n.ins[i]]] == 1`).
If a node is evaluated, its output is marked dirty.
By default, this array is all-1, therefore every node is evaluated every time by `c_prop()`.
Enable incremental simulation by setting this array to all-0, change signal values in `c` and set `c_dirty` to 1 for all changed signals.
Next call to `c_prop()` will only evaluate nodes that read from the changed signals.
To restore the original simulation state, revert the signal value changes in `c`, and call `c_prop()` again.
Caveats:
- Injected changes that are located downstream of other changes will have no effect.
- Only works with `c_reuse=False` (default).
"""
self.s_assign = np.zeros((self.s_len, self.sims), dtype=np.uint8)
"""Logic values assigned to the ports and flip-flops of the circuit.
@ -404,7 +371,7 @@ class LogicSim2V(sim.SimOps): @@ -404,7 +371,7 @@ class LogicSim2V(sim.SimOps):
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
fault_mask2[:len(fault_mask)] = fault_mask
fault_mask = fault_mask2
c_prop_2v_cpu(self.ops, self.c_locs, self.c, self.c_dirty, int(fault_line), fault_mask, int(fault_model))
c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
def c_to_s(self):
"""Captures the results of the combinational portion into ``self.s_result``.
@ -438,11 +405,9 @@ class LogicSim2V(sim.SimOps): @@ -438,11 +405,9 @@ class LogicSim2V(sim.SimOps):
@numba.njit
def c_prop_2v_cpu(ops, c_locs, c, c_dirty, fault_line, fault_mask, fault_model):
def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
for op, o0l, i0l, i1l, i2l, i3l in ops[:,:6]:
o0, i0, i1, i2, i3 = [c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
if fault_line < 0 or o0l != fault_line: # fault injection forces node evaluation
if not (c_dirty[i0] | c_dirty[i1] | c_dirty[i2] | c_dirty[i3]): continue
if op == sim.BUF1: c[o0]=c[i0]
elif op == sim.INV1: c[o0] = ~c[i0]
elif op == sim.AND2: c[o0] = c[i0] & c[i1]
@ -482,9 +447,8 @@ def c_prop_2v_cpu(ops, c_locs, c, c_dirty, fault_line, fault_mask, fault_model): @@ -482,9 +447,8 @@ def c_prop_2v_cpu(ops, c_locs, c, c_dirty, fault_line, fault_mask, fault_model):
c[o0] = c[o0] & ~fault_mask
elif fault_model == 1:
c[o0] = c[o0] | fault_mask
elif fault_model == 2:
else:
c[o0] = c[o0] ^ fault_mask
c_dirty[o0] = 1
class LogicSim4V(sim.SimOps):
@ -509,7 +473,7 @@ class LogicSim4V(sim.SimOps): @@ -509,7 +473,7 @@ class LogicSim4V(sim.SimOps):
Storage locations are indirectly addressed.
Data for line `l` is in `self.c[self.c_locs[l]]`.
"""
self.s_assign = np.full((self.s_len, self.sims), logic.UNASSIGNED, dtype=np.uint8)
self.s_assign = np.zeros((self.s_len, self.sims), dtype=np.uint8)
"""Logic values assigned to the ports and flip-flops of the circuit.
The simulator reads (P)PI values from here.
Values assigned to PO positions are ignored.
@ -517,7 +481,7 @@ class LogicSim4V(sim.SimOps): @@ -517,7 +481,7 @@ class LogicSim4V(sim.SimOps):
First index is the port position (defined by `self.circuit.s_nodes`), second
index is the pattern index (0 ... `self.sims-1`).
"""
self.s_result = np.full((self.s_len, self.sims), logic.UNASSIGNED, dtype=np.uint8)
self.s_result = np.zeros((self.s_len, self.sims), dtype=np.uint8)
"""Logic values at the ports and flip-flops of the circuit after simulation.
The simulator writes (P)PO values here.
Values assigned to PI positions are ignored.
@ -528,7 +492,7 @@ class LogicSim4V(sim.SimOps): @@ -528,7 +492,7 @@ class LogicSim4V(sim.SimOps):
self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
def __repr__(self):
return f'{{name: "{self.circuit.name}", sims: {self.sims}, s_len: {self.s_len}, c_bytes: {eng(self.c.nbytes)}}}'
return f'{{name: "{self.circuit.name}", sims: {self.sims}, c_bytes: {eng(self.c.nbytes)}}}'
def s_to_c(self):
"""Assigns the values from ``self.s_assign`` to the inputs of the combinational portion.
@ -775,7 +739,7 @@ class LogicSim6V(sim.SimOps): @@ -775,7 +739,7 @@ class LogicSim6V(sim.SimOps):
nbytes = cdiv(sims, 8)
self.c = np.zeros((self.c_len, 3, nbytes), dtype=np.uint8)
self.s = np.full((2, self.s_len, self.sims), logic.UNASSIGNED, dtype=np.uint8)
self.s = np.zeros((2, self.s_len, self.sims), dtype=np.uint8)
"""Logic values of the sequential elements (flip-flops) and ports.
It is a pair of arrays in mv storage format:

17
src/kyupy/sdf.py

@ -59,7 +59,7 @@ class DelayFile: @@ -59,7 +59,7 @@ class DelayFile:
if name not in circuit.cells: name = name.replace('[', '_').replace(']', '_')
return circuit.cells.get(name, None)
delays = np.zeros((len(circuit.lines), 2, 2, 3), dtype=np.float32) # dataset last during construction.
delays = np.zeros((len(circuit.lines), 2, 2, 3)) # dataset last during construction.
with log.limit(50):
for name, iopaths in self.cells.items():
@ -101,19 +101,14 @@ class DelayFile: @@ -101,19 +101,14 @@ class DelayFile:
* Axis 3: polarity of the transition, 0='rising/posedge', 1='falling/negedge'
"""
delays = np.zeros((len(circuit.lines), 2, 2, 3), dtype=np.float32) # dataset last during construction.
def split_node_port(n_str):
if (split := n_str.rfind('.')) > 0 or (split := n_str.rfind('/')) > 0:
return n_str[:split], n_str[split+1:]
return (n_str, None)
delays = np.zeros((len(circuit.lines), 2, 2, 3)) # dataset last during construction.
nonfork_annotations = 0
for n1, n2, *delvals in self._interconnects:
delvals = [d if len(d) > 0 else [0, 0, 0] for d in delvals]
if max(max(delvals)) == 0: continue
cn1, pn1 = split_node_port(n1)
cn2, pn2 = split_node_port(n2)
cn1, pn1 = (n1, None) if (slash := n1.rfind('/')) < 0 else (n1[:slash], n1[slash+1:])
cn2, pn2 = (n2, None) if (slash := n2.rfind('/')) < 0 else (n2[:slash], n2[slash+1:])
cn1 = cn1.replace('\\','')
cn2 = cn2.replace('\\','')
c1, c2 = circuit.cells[cn1], circuit.cells[cn2]
@ -222,9 +217,9 @@ def parse(text): @@ -222,9 +217,9 @@ def parse(text):
return Lark(GRAMMAR, parser="lalr", transformer=SdfTransformer()).parse(text)
def load(file) -> DelayFile:
def load(file):
"""Parses the contents of ``file`` and returns a :class:`DelayFile` object.
Files with `.gz`-suffix are decompressed on-the-fly.
"""
return parse(readtext(file)) # type: ignore
return parse(readtext(file))

50
src/kyupy/sim.py

@ -6,7 +6,6 @@ import numpy as np @@ -6,7 +6,6 @@ import numpy as np
from . import log
from .circuit import Circuit
from .techlib import KYUPY
BUF1 = np.uint16(0b1010_1010_1010_1010)
INV1 = ~BUF1
@ -160,7 +159,7 @@ class SimOps: @@ -160,7 +159,7 @@ class SimOps:
"""A static scheduler that translates a Circuit into a topologically sorted list of basic logic operations (self.ops) and
a memory mapping (self.c_locs, self.c_caps) for use in simulators.
:param circuit: The circuit to create a schedule for. Must contain cells from KYUPY techlib only.
:param circuit: The circuit to create a schedule for.
:param strip_forks: If enabled, the scheduler will not include fork nodes to safe simulation time.
Stripping forks will cause interconnect delay annotations of lines read by fork nodes to be ignored.
:param c_reuse: If enabled, memory of intermediate signal waveforms will be re-used. This greatly reduces
@ -168,7 +167,7 @@ class SimOps: @@ -168,7 +167,7 @@ class SimOps:
"""
def __init__(self, circuit: Circuit, c_caps=1, c_caps_min=1, a_ctrl=None, c_reuse=False, strip_forks=False):
self.circuit = circuit
self.s_len = len(circuit.s_nodes(KYUPY))
self.s_len = len(circuit.s_nodes)
if isinstance(c_caps, int):
c_caps = [c_caps] * (len(circuit.lines)+3)
@ -188,11 +187,11 @@ class SimOps: @@ -188,11 +187,11 @@ class SimOps:
# ALAP-toposort the circuit into self.ops
levels = []
ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes(KYUPY)))
root_nodes = set([n for n in circuit.nodes if len(n.outs) == 0 or KYUPY.is_dff(n.kind)]) # start from POs (that are unused by circuit itself), PPOs, and any dangling nodes
ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes))
root_nodes = set([n for n in circuit.s_nodes if len(n.ins) > 0] + [n for n in circuit.nodes if len(n.outs) == 0]) # start from POs, PPOs, and any dangling nodes
readers = np.array([1 if l.reader in root_nodes else len(l.reader.outs) for l in circuit.lines], dtype=np.int32) # for ref-counting forks
level_lines = [n.ins[0] for n in sorted(root_nodes, key=lambda n: n.index) if len(n.ins) > 0 ]
level_lines = [n.ins[0] for n in root_nodes]
# FIXME: Should probably instanciate buffers for PPOs and attach DFF clocks
while len(level_lines) > 0: # traverse the circuit level-wise back towards (P)PIs
@ -201,15 +200,11 @@ class SimOps: @@ -201,15 +200,11 @@ class SimOps:
for l in level_lines:
n = l.driver
assert len(n.ins) <= 4, f'Too many input pins for node {n}. Map circuit to KYUPY techlib first using resolve_tlib_cells().'
if len(n.ins) > 4:
log.warn(f'too many input pins: {n}')
in_idxs = [n.ins[x].index if len(n.ins) > x and n.ins[x] is not None else self.zero_idx for x in [0,1,2,3]]
if n in ppio2idx:
in_idxs[1] = self.zero_idx
if 'dff' in n.kind.lower() or in_idxs[0] == self.zero_idx:
in_idxs[0] = self.ppi_offset + ppio2idx[n]
else: # this is a PO that is also used within the circuit. Treat as normal fork with ref counting. TODO: check strip_forks=True case for this.
readers[n.ins[0]] -= 1
if readers[n.ins[0]] == 0: prev_level_lines.append(n.ins[0])
in_idxs[0] = self.ppi_offset + ppio2idx[n]
if l.driver_pin == 1 and 'dff' in n.kind.lower(): # second output of DFF is inverted
level_ops.append((INV1, l.index, *in_idxs, *a_ctrl[l]))
else:
@ -230,8 +225,10 @@ class SimOps: @@ -230,8 +225,10 @@ class SimOps:
if in_idxs[2] == self.zero_idx:
sp = prims[2]
break
assert sp is not None, f'Unsupported node type {n}. Map circuit to KYUPY techlib first using resolce_tlib_cells().'
level_ops.append((sp, l.index, *in_idxs, *a_ctrl[l]))
if sp is None:
log.warn(f'ignored cell of unknown type: {n}')
else:
level_ops.append((sp, l.index, *in_idxs, *a_ctrl[l]))
if len(level_ops) > 0: levels.append(level_ops)
level_lines = prev_level_lines
@ -274,7 +271,7 @@ class SimOps: @@ -274,7 +271,7 @@ class SimOps:
ref_count[self.tmp2_idx] += 1
# allocate and keep memory for PI/PPI, keep memory for PO/PPO (allocated later)
for i, n in enumerate(circuit.s_nodes(KYUPY)):
for i, n in enumerate(circuit.s_nodes):
if 'dff' in n.kind.lower() or len(n.ins) == 0: # PPI or PI
self.c_locs[self.ppi_offset + i], self.c_caps[self.ppi_offset + i] = h.alloc(c_caps_min), c_caps_min
ref_count[self.ppi_offset + i] += 1
@ -313,15 +310,10 @@ class SimOps: @@ -313,15 +310,10 @@ class SimOps:
self.c_locs[lidx], self.c_caps[lidx] = self.c_locs[stem], self.c_caps[stem]
# copy memory location to PO/PPO area
for i, n in enumerate(circuit.s_nodes(KYUPY)):
for i, n in enumerate(circuit.s_nodes):
if len(n.ins) > 0:
self.c_locs[self.ppo_offset + i], self.c_caps[self.ppo_offset + i] = self.c_locs[n.ins[0]], self.c_caps[n.ins[0]]
# sanity check
for oidx, io in zip(*np.nonzero(self.c_locs[self.ops[:, 1:6]] < 0)):
log.error(f'unallocated {["o0","i0","i1","i2","i3"][io]} in op {self.ops[oidx]}')
assert np.min(self.c_locs[self.ops[:, 1:6]]) >= 0, "Unallocated signal lines."
# line use information
self.line_use_start = np.full(self.c_locs_len, -1, dtype=np.int32)
self.line_use_stop = np.full(self.c_locs_len, len(self.levels), dtype=np.int32)
@ -338,16 +330,11 @@ class SimOps: @@ -338,16 +330,11 @@ class SimOps:
self.prim_counts = dict(d)
self.pi_s_locs = np.flatnonzero(self.c_locs[self.ppi_offset+np.arange(len(self.circuit.io_nodes))] >= 0)
"""Locations in pattern array (axis 0) of all primary inputs (excluding flip-flops)."""
self.po_s_locs = np.flatnonzero(self.c_locs[self.ppo_offset+np.arange(len(self.circuit.io_nodes))] >= 0)
"""Locations in pattern array (axis 0) of all primary outputs (excluding flip-flops)."""
self.ppio_s_locs = np.arange(len(self.circuit.io_nodes), self.s_len)
"""Locations in pattern array (axis 0) of all flip-flops. Pseudo-primary inputs (PPI) and pseudo-primary outputs (PPO) share the same location."""
self.pippi_s_locs = np.concatenate([self.pi_s_locs, self.ppio_s_locs])
"""Locations in pattern array (axis 0) of all primary and pseudo-primary inputs (input ports and flip-flops)."""
self.poppo_s_locs = np.concatenate([self.po_s_locs, self.ppio_s_locs])
"""Locations in pattern array (axis 0) of all primary and pseudo-primary outputs (output ports and flip-flops)."""
self.pi_c_locs = self.c_locs[self.ppi_offset+self.pi_s_locs]
self.po_c_locs = self.c_locs[self.ppo_offset+self.po_s_locs]
@ -356,12 +343,3 @@ class SimOps: @@ -356,12 +343,3 @@ class SimOps:
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])
def cone_ops(self, origin_lines: set[int]):
active_lines = {l for l in origin_lines}
ops = []
for kind, out0, in0, in1, in2, in3, *params in self.ops:
if in0 in active_lines or in1 in active_lines or in2 in active_lines or in3 in active_lines or out0 in origin_lines:
ops.append((kind, out0, in0, in1, in2, in3, *params))
active_lines.add(out0)
return np.asarray(ops, dtype=np.int32)

12
src/kyupy/stil.py

@ -106,7 +106,7 @@ class StilFile: @@ -106,7 +106,7 @@ class StilFile:
tests[pi_map, i] = logic.mvarray(p.capture['_pi'][0])
return tests
def tests_loc(self, circuit, tlib, init_filter=None, launch_filter=None):
def tests_loc(self, circuit, init_filter=None, launch_filter=None):
"""Assembles and returns a LoC scan test pattern set for given circuit.
This function assumes a launch-on-capture (LoC) delay test.
@ -139,9 +139,7 @@ class StilFile: @@ -139,9 +139,7 @@ class StilFile:
init[scan_maps[si_port], i] = pattern
init[pi_map, i] = logic.mvarray(p.launch['_pi'][0] if '_pi' in p.launch else p.capture['_pi'][0])
if init_filter: init = init_filter(init)
circuit_resolved = circuit.copy()
circuit_resolved.resolve_tlib_cells(tlib)
sim8v = LogicSim(circuit_resolved, init.shape[-1], m=8)
sim8v = LogicSim(circuit, init.shape[-1], m=8)
sim8v.s[0] = logic.mv_to_bp(init)
sim8v.s_to_c()
sim8v.c_prop()
@ -265,12 +263,12 @@ GRAMMAR = r""" @@ -265,12 +263,12 @@ GRAMMAR = r"""
"""
def parse(text) -> StilFile:
def parse(text):
"""Parses the given ``text`` and returns a :class:`StilFile` object."""
return Lark(GRAMMAR, parser="lalr", transformer=StilTransformer()).parse(text) # type: ignore
return Lark(GRAMMAR, parser="lalr", transformer=StilTransformer()).parse(text)
def load(file) -> StilFile:
def load(file):
"""Parses the contents of ``file`` and returns a :class:`StilFile` object.
Files with `.gz`-suffix are decompressed on-the-fly.

233
src/kyupy/techlib.py

@ -10,6 +10,7 @@ from itertools import product @@ -10,6 +10,7 @@ from itertools import product
from . import bench
class TechLib:
"""Class for standard cell library definitions.
@ -38,11 +39,9 @@ class TechLib: @@ -38,11 +39,9 @@ class TechLib:
else:
pin_dict[n.name] = (o_idx, True)
o_idx += 1
has_dff = 'DFF' in set(n.kind for n in c.cells.values())
has_latch = 'LATCH' in set(n.kind for n in c.cells.values())
parts = [s[1:-1].split(',') if s[0] == '{' else [s] for s in re.split(r'({[^}]+})', c.name) if len(s) > 0]
for name in [''.join(item) for item in product(*parts)]:
self.cells[name] = (c, pin_dict, has_dff, has_latch)
self.cells[name] = (c, pin_dict)
def pin_index(self, kind, pin):
"""Returns a pin list position for a given node kind and pin name."""
@ -50,39 +49,19 @@ class TechLib: @@ -50,39 +49,19 @@ class TechLib:
assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}'
return self.cells[kind][1][pin][0]
def pin_name(self, kind: str, pos: int, output=False) -> str:
def pin_name(self, kind, pos, output=False):
"""Returns the pin name for a given node kind, list position, and direction."""
assert kind in self.cells, f'Unknown cell: {kind}'
for name, (ppos, isout) in self.cells[kind][1].items():
if isout == output and ppos == pos:
return name
raise ValueError(f'Invalid {"output" if output else "input"} pin position {pos} for cell: {kind}')
return None
def pin_is_output(self, kind, pin):
"""Returns True, if given pin name of a node kind is an output."""
assert kind in self.cells, f'Unknown cell: {kind}'
assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}'
return self.cells[kind][1][pin][1]
def is_dff(self, kind):
"""Returns True, if given node kind is a d-flip-flop."""
if kind == '__fork__': return False
if kind == '__const0__': return False
if kind == '__const1__': return False
if kind == 'input': return False
if kind == 'output': return False
assert kind in self.cells, f'Unknown cell: {kind}'
return self.cells[kind][2]
def is_latch(self, kind):
"""Returns True, if given node kind is a latch."""
if kind == '__fork__': return False
if kind == '__const0__': return False
if kind == '__const1__': return False
if kind == 'input': return False
if kind == 'output': return False
assert kind in self.cells, f'Unknown cell: {kind}'
return self.cells[kind][3]
KYUPY = TechLib(r"""
@ -120,7 +99,6 @@ AOI211 input(i0,i1,i2,i3) output(o) o=AOI211(i0,i1,i2,i3) ; @@ -120,7 +99,6 @@ AOI211 input(i0,i1,i2,i3) output(o) o=AOI211(i0,i1,i2,i3) ;
OAI211 input(i0,i1,i2,i3) output(o) o=OAI211(i0,i1,i2,i3) ;
MUX21 input(i0,i1,i2) output(o) o=MUX21(i0,i1,i2) ;
DFF input(D,CLK) output(Q) Q=DFF(D,CLK) ;
LATCH input(D,CLK) output(Q) Q=LATCH(D,CLK) ;
""")
"""A synthetic library of all KyuPy simulation primitives.
"""
@ -150,7 +128,7 @@ OAI21X1 input(A0,A1,B0) output(Y) Y=OAI21(A0,A1,B0) ; @@ -150,7 +128,7 @@ OAI21X1 input(A0,A1,B0) output(Y) Y=OAI21(A0,A1,B0) ;
OAI22X1 input(A0,A1,B0,B1) output(Y) Y=OAI22(A0,A1,B0,B1) ;
OAI33X1 input(A0,A1,A2,B0,B1,B2) output(Y) AA=OR2(A0,A1) BB=OR2(B0,B1) Y=OAI22(AA,A2,BB,B2) ;
ADDFX1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ;
ADDHX1 input(A,B) output(CO,S) CO=AND2(A,B) S=XOR2(A,B) ;
ADDHX1 input(A,B) output(CO,S) CO=XOR2(A,B) S=AND2(A,B) ;
DFFX1 input(CK,D) output(Q,QN) Q=DFF(D,CK) QN=INV1(Q) ;
DFFSRX1 input(CK,D,RN,SN) output(Q,QN) DR=AND2(D,RN) SET=INV1(SN) DRS=OR2(DR,SET) Q=DFF(DRS,CK) QN=INV1(Q) ;
@ -194,7 +172,7 @@ XNOR2_X1 input(A1,A2) output(ZN) ZN=XNOR2(A1,A2) ; @@ -194,7 +172,7 @@ XNOR2_X1 input(A1,A2) output(ZN) ZN=XNOR2(A1,A2) ;
MUX2_X1 input(I0,I1,S) output(Z) Z=MUX21(I0,I1,S) ;
HA_X1 input(A,B) output(CO,S) CO=AND2(A,B) S=XOR2(A,B) ;
HA_X1 input(A,B) output(CO,S) CO=XOR2(A,B) S=AND2(A,B) ;
FA_X1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(CI,A,B) ;
AOI21_X{1,2} input(A1,A2,B) output(ZN) ZN=AOI21(A1,A2,B) ;
@ -247,7 +225,7 @@ XNOR2_X{1,2} input(A,B) output(ZN) ZN=XNOR2(A,B) ; @@ -247,7 +225,7 @@ XNOR2_X{1,2} input(A,B) output(ZN) ZN=XNOR2(A,B) ;
MUX2_X{1,2} input(A,B,S) output(Z) Z=MUX21(A,B,S) ;
HA_X1 input(A,B) output(CO,S) CO=AND2(A,B) S=XOR2(A,B) ;
HA_X1 input(A,B) output(CO,S) CO=XOR2(A,B) S=AND2(A,B) ;
FA_X1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(CI,A,B) ;
AOI21_X{1,2,4} input(A,B1,B2) output(ZN) ZN=AOI21(B1,B2,A) ;
@ -362,7 +340,7 @@ MUX41X{1,2}$ input(A1,A2,A3,A4,S0,S1) output(Y) A=MUX21(A1,A2,S0) B=MUX21(A3,A4, @@ -362,7 +340,7 @@ MUX41X{1,2}$ input(A1,A2,A3,A4,S0,S1) output(Y) A=MUX21(A1,A2,S0) B=MUX21(A3,A4,
DEC24X{1,2}$ input(A0,A1) output(Y0,Y1,Y2,Y3) A0B=INV1(A0) A1B=INV1(A1) Y0=NOR2(A0,A1) Y1=AND(A0,A1B) Y2=AND(A0B,A1) Y3=AND(A0,A1) ;
FADDX{1,2}$ input(A,B,CI) output(S,CO) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ;
HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=AND2(A0,B0) SO=XOR2(A0,B0) ;
HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=XOR2(A0,B0) SO=AND2(A0,B0) ;
{,AO}DFFARX{1,2}$ input(D,CLK,RSTB) output(Q,QN) DR=AND2(D,RSTB) Q=DFF(DR,CLK) QN=INV1(Q) ;
DFFASRX{1,2}$ input(D,CLK,RSTB,SETB) output(Q,QN) DR=AND2(D,RSTB) SET=INV1(SETB) DRS=OR2(DR,SET) Q=DFF(DRS,CLK) QN=INV1(Q) ;
@ -452,7 +430,7 @@ MUX41X{1,2}$ input(IN1,IN2,IN3,IN4,S0,S1) output(Q) A=MUX21(IN1,IN2,S0) B=MUX21( @@ -452,7 +430,7 @@ MUX41X{1,2}$ input(IN1,IN2,IN3,IN4,S0,S1) output(Q) A=MUX21(IN1,IN2,S0) B=MUX21(
DEC24X{1,2}$ input(IN1,IN2) output(Q0,Q1,Q2,Q3) IN1B=INV1(IN1) IN2B=INV1(IN2) Q0=NOR2(IN1,IN2) Q1=AND(IN1,IN2B) Q2=AND(IN1B,IN2) Q3=AND(IN1,IN2) ;
FADDX{1,2}$ input(A,B,CI) output(S,CO) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ;
HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=AND2(A0,B0) SO=XOR2(A0,B0) ;
HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=XOR2(A0,B0) SO=AND2(A0,B0) ;
{,AO}DFFARX{1,2}$ input(D,CLK,RSTB) output(Q,QN) DR=AND2(D,RSTB) Q=DFF(DR,CLK) QN=INV1(Q) ;
DFFASRX{1,2}$ input(D,CLK,RSTB,SETB) output(Q,QN) DR=AND2(D,RSTB) SET=INV1(SETB) DRS=OR2(DR,SET) Q=DFF(DRS,CLK) QN=INV1(Q) ;
@ -472,196 +450,3 @@ LATCHX{1,2}$ input(D,CLK) output(Q,QN) Q=LATCH(D,CLK) QN=INV1(Q) ; @@ -472,196 +450,3 @@ LATCHX{1,2}$ input(D,CLK) output(Q,QN) Q=LATCH(D,CLK) QN=INV1(Q) ;
"""The SAED 90nm educational technology library.
It defines all cells except: negative-edge flip-flops, tri-state, latches, clock gating, level shifters
"""
SKY130 = TechLib(r"""
$decap_{3,4,6,8,12} ;
$fill_{1,2,4,8} ;
$tap_{1,2} ;
$tapvgnd_1 ;
$tapvgnd2_1 ;
$tapvpwrvgnd_1 ;
$lpflow_decapkapwr_{3,4,6,8,12} ;
$lpflow_bleeder_1 input(SHORT) ;
$conb_1 output(HI,LO) HI=__const1__() LO=__const0__() ;
$macro_sparecell output(LO) LO=__const0__() ;
$diode_2 input(DIODE) ;
$probe_p_8 input(A) output(X) X=BUF1(A) ;
$probec_p_8 input(A) output(X) X=BUF1(A) ;
$inv_{1,2,4,6,8,12,16} input(A) output(Y) Y=INV1(A) ;
$buf_{1,2,4,6,8,12,16} input(A) output(X) X=BUF1(A) ;
$bufbuf_{8,16} input(A) output(X) X=BUF1(A) ;
$bufinv_{8,16} input(A) output(Y) Y=INV1(A) ;
$clkbuf_{1,2,4,8,16} input(A) output(X) X=BUF1(A) ;
$clkinv_{1,2,4,8,16} input(A) output(Y) Y=INV1(A) ;
$clkinvlp_{2,4} input(A) output(Y) Y=INV1(A) ;
$clkdlybuf4s15_{1,2} input(A) output(X) X=BUF1(A) ;
$clkdlybuf4s18_{1,2} input(A) output(X) X=BUF1(A) ;
$clkdlybuf4s25_{1,2} input(A) output(X) X=BUF1(A) ;
$clkdlybuf4s50_{1,2} input(A) output(X) X=BUF1(A) ;
$dlygate4sd1_1 input(A) output(X) X=BUF1(A) ;
$dlygate4sd2_1 input(A) output(X) X=BUF1(A) ;
$dlygate4sd3_1 input(A) output(X) X=BUF1(A) ;
$dlymetal6s2s_1 input(A) output(X) X=BUF1(A) ;
$dlymetal6s4s_1 input(A) output(X) X=BUF1(A) ;
$dlymetal6s6s_1 input(A) output(X) X=BUF1(A) ;
$lpflow_clkbufkapwr_{1,2,4,8,16} input(A) output(X) X=BUF1(A) ;
$lpflow_clkinvkapwr_{1,2,4,8,16} input(A) output(Y) Y=INV1(A) ;
$lpflow_lsbuf_lh_hl_isowell_tap_{1,2,4} input(A) output(X) X=BUF1(A) ;
$lpflow_lsbuf_lh_isowell_4 input(A) output(X) X=BUF1(A) ;
$lpflow_lsbuf_lh_isowell_tap_{1,2,4} input(A) output(X) X=BUF1(A) ;
$and2_{0,1,2,4} input(A,B) output(X) X=AND2(A,B) ;
$and2b_{1,2,4} input(A_N,B) output(X) AN=INV1(A_N) X=AND2(AN,B) ;
$and3_{1,2,4} input(A,B,C) output(X) X=AND3(A,B,C) ;
$and3b_{1,2,4} input(A_N,B,C) output(X) AN=INV1(A_N) X=AND3(AN,B,C) ;
$and4_{1,2,4} input(A,B,C,D) output(X) X=AND4(A,B,C,D) ;
$and4b_{1,2,4} input(A_N,B,C,D) output(X) AN=INV1(A_N) X=AND4(AN,B,C,D) ;
$and4bb_{1,2,4} input(A_N,B_N,C,D) output(X) AN=INV1(A_N) BN=INV1(B_N) X=AND4(AN,BN,C,D) ;
$or2_{0,1,2,4} input(A,B) output(X) X=OR2(A,B) ;
$or2b_{1,2,4} input(A,B_N) output(X) BN=INV1(B_N) X=OR2(A,BN) ;
$or3_{1,2,4} input(A,B,C) output(X) X=OR3(A,B,C) ;
$or3b_{1,2,4} input(A,B,C_N) output(X) CN=INV1(C_N) X=OR3(A,B,CN) ;
$or4_{1,2,4} input(A,B,C,D) output(X) X=OR4(A,B,C,D) ;
$or4b_{1,2,4} input(A,B,C,D_N) output(X) DN=INV1(D_N) X=OR4(A,B,C,DN) ;
$or4bb_{1,2,4} input(A,B,C_N,D_N) output(X) CN=INV1(C_N) DN=INV1(D_N) X=OR4(A,B,CN,DN) ;
$nand2_{1,2,4,8} input(A,B) output(Y) Y=NAND2(A,B) ;
$nand2b_{1,2,4} input(A_N,B) output(Y) AN=INV1(A_N) Y=NAND2(AN,B) ;
$nand3_{1,2,4} input(A,B,C) output(Y) Y=NAND3(A,B,C) ;
$nand3b_{1,2,4} input(A_N,B,C) output(Y) AN=INV1(A_N) Y=NAND3(AN,B,C) ;
$nand4_{1,2,4} input(A,B,C,D) output(Y) Y=NAND4(A,B,C,D) ;
$nand4b_{1,2,4} input(A_N,B,C,D) output(Y) AN=INV1(A_N) Y=NAND4(AN,B,C,D) ;
$nand4bb_{1,2,4} input(A_N,B_N,C,D) output(Y) AN=INV1(A_N) BN=INV1(B_N) Y=NAND4(AN,BN,C,D) ;
$nor2_{1,2,4,8} input(A,B) output(Y) Y=NOR2(A,B) ;
$nor2b_{1,2,4} input(A,B_N) output(Y) BN=INV1(B_N) Y=NOR2(A,BN) ;
$nor3_{1,2,4} input(A,B,C) output(Y) Y=NOR3(A,B,C) ;
$nor3b_{1,2,4} input(A,B,C_N) output(Y) CN=INV1(C_N) Y=NOR3(A,B,CN) ;
$nor4_{1,2,4} input(A,B,C,D) output(Y) Y=NOR4(A,B,C,D) ;
$nor4b_{1,2,4} input(A,B,C,D_N) output(Y) DN=INV1(D_N) Y=NOR4(A,B,C,DN) ;
$nor4bb_{1,2,4} input(A,B,C_N,D_N) output(Y) CN=INV1(C_N) DN=INV1(D_N) Y=NOR4(A,B,CN,DN) ;
$xor2_{1,2,4} input(A,B) output(X) X=XOR2(A,B) ;
$xor3_{1,2,4} input(A,B,C) output(X) X=XOR3(A,B,C) ;
$xnor2_{1,2,4} input(A,B) output(Y) Y=XNOR2(A,B) ;
$xnor3_{1,2,4} input(A,B,C) output(X) X=XNOR3(A,B,C) ;
$maj3_{1,2,4} input(A,B,C) output(X) AB=AND2(A,B) BC=AND2(B,C) AC=AND2(A,C) X=OR3(AB,BC,AC) ;
$mux2_{1,2,4,8} input(A0,A1,S) output(X) X=MUX21(A0,A1,S) ;
$mux2i_{1,2,4} input(A0,A1,S) output(Y) M=MUX21(A0,A1,S) Y=INV1(M) ;
$mux4_{1,2,4} input(A0,A1,A2,A3,S0,S1) output(X) M0=MUX21(A0,A1,S0) M1=MUX21(A2,A3,S0) X=MUX21(M0,M1,S1) ;
$ha_{1,2,4} input(A,B) output(COUT,SUM) SUM=XOR2(A,B) COUT=AND2(A,B) ;
$fa_{1,2,4} input(A,B,CIN) output(COUT,SUM) AB=XOR2(A,B) SUM=XOR2(AB,CIN) COUT=AO22(A,B,AB,CIN) ;
$fah_1 input(A,B,CI) output(COUT,SUM) AB=XOR2(A,B) SUM=XOR2(AB,CI) COUT=AO22(A,B,AB,CI) ;
$fahcin_1 input(A,B,CIN) output(COUT,SUM) AB=XOR2(A,B) SUM=XOR2(AB,CIN) COUT=AO22(A,B,AB,CIN) ;
$fahcon_1 input(A,B,CI) output(COUT_N,SUM) AB=XOR2(A,B) SUM=XOR2(AB,CI) T=AO22(A,B,AB,CI) COUT_N=INV1(T) ;
$a21o_{1,2,4} input(A1,A2,B1) output(X) X=AO21(A1,A2,B1) ;
$a21bo_{1,2,4} input(A1,A2,B1_N) output(X) BN=INV1(B1_N) X=AO21(A1,A2,BN) ;
$a22o_{1,2,4} input(A1,A2,B1,B2) output(X) X=AO22(A1,A2,B1,B2) ;
$a2bb2o_{1,2,4} input(A1_N,A2_N,B1,B2) output(X) AN1=INV1(A1_N) AN2=INV1(A2_N) X=AO22(AN1,AN2,B1,B2) ;
$a211o_{1,2,4} input(A1,A2,B1,C1) output(X) X=AO211(A1,A2,B1,C1) ;
$a221o_{1,2,4} input(A1,A2,B1,B2,C1) output(X) T=AO22(A1,A2,B1,B2) X=OR2(T,C1) ;
$a31o_{1,2,4} input(A1,A2,A3,B1) output(X) AA=AND3(A1,A2,A3) X=OR2(AA,B1) ;
$a311o_{1,2,4} input(A1,A2,A3,B1,C1) output(X) T=AND2(A1,A2) X=AO211(T,A3,B1,C1) ;
$a32o_{1,2,4} input(A1,A2,A3,B1,B2) output(X) T=AND2(A1,A2) X=AO22(T,A3,B1,B2) ;
$a41o_{1,2,4} input(A1,A2,A3,A4,B1) output(X) T1=AND2(A1,A2) T2=AND2(T1,A3) X=AO21(T2,A4,B1) ;
$a2111o_{1,2,4} input(A1,A2,B1,C1,D1) output(X) T=AO211(A1,A2,B1,C1) X=OR2(T,D1) ;
$a21oi_{1,2,4} input(A1,A2,B1) output(Y) Y=AOI21(A1,A2,B1) ;
$a21boi_{0,1,2,4} input(A1,A2,B1_N) output(Y) BN=INV1(B1_N) Y=AOI21(A1,A2,BN) ;
$a22oi_{1,2,4} input(A1,A2,B1,B2) output(Y) Y=AOI22(A1,A2,B1,B2) ;
$a2bb2oi_{1,2,4} input(A1_N,A2_N,B1,B2) output(Y) AN1=INV1(A1_N) AN2=INV1(A2_N) Y=AOI22(AN1,AN2,B1,B2) ;
$a211oi_{1,2,4} input(A1,A2,B1,C1) output(Y) Y=AOI211(A1,A2,B1,C1) ;
$a221oi_{1,2,4} input(A1,A2,B1,B2,C1) output(Y) T=AO22(A1,A2,B1,B2) Y=NOR2(T,C1) ;
$a222oi_1 input(A1,A2,B1,B2,C1,C2) output(Y) AB=AO22(A1,A2,B1,B2) Y=AOI21(C1,C2,AB) ;
$a31oi_{1,2,4} input(A1,A2,A3,B1) output(Y) AA=AND3(A1,A2,A3) Y=NOR2(AA,B1) ;
$a311oi_{1,2,4} input(A1,A2,A3,B1,C1) output(Y) T=AND2(A1,A2) Y=AOI211(T,A3,B1,C1) ;
$a32oi_{1,2,4} input(A1,A2,A3,B1,B2) output(Y) T=AND2(A1,A2) Y=AOI22(T,A3,B1,B2) ;
$a41oi_{1,2,4} input(A1,A2,A3,A4,B1) output(Y) T1=AND2(A1,A2) T2=AND2(T1,A3) Y=AOI21(T2,A4,B1) ;
$a2111oi_{0,1,2,4} input(A1,A2,B1,C1,D1) output(Y) T=AO211(A1,A2,B1,C1) Y=NOR2(T,D1) ;
$o21a_{1,2,4} input(A1,A2,B1) output(X) X=OA21(A1,A2,B1) ;
$o21ba_{1,2,4} input(A1,A2,B1_N) output(X) BN=INV1(B1_N) X=OA21(A1,A2,BN) ;
$o22a_{1,2,4} input(A1,A2,B1,B2) output(X) X=OA22(A1,A2,B1,B2) ;
$o2bb2a_{1,2,4} input(A1_N,A2_N,B1,B2) output(X) T=NAND2(A1_N,A2_N) X=OA21(B1,B2,T) ;
$o211a_{1,2,4} input(A1,A2,B1,C1) output(X) X=OA211(A1,A2,B1,C1) ;
$o221a_{1,2,4} input(A1,A2,B1,B2,C1) output(X) T=OA22(A1,A2,B1,B2) X=AND2(T,C1) ;
$o31a_{1,2,4} input(A1,A2,A3,B1) output(X) T=OR3(A1,A2,A3) X=AND2(T,B1) ;
$o311a_{1,2,4} input(A1,A2,A3,B1,C1) output(X) T=OR2(A1,A2) X=OA211(T,A3,B1,C1) ;
$o32a_{1,2,4} input(A1,A2,A3,B1,B2) output(X) T=OR3(A1,A2,A3) X=OA21(B1,B2,T) ;
$o41a_{1,2,4} input(A1,A2,A3,A4,B1) output(X) T=OR4(A1,A2,A3,A4) X=AND2(T,B1) ;
$o2111a_{1,2,4} input(A1,A2,B1,C1,D1) output(X) T=OA211(A1,A2,B1,C1) X=AND2(T,D1) ;
$o21ai_{0,1,2,4} input(A1,A2,B1) output(Y) Y=OAI21(A1,A2,B1) ;
$o21bai_{1,2,4} input(A1,A2,B1_N) output(Y) BN=INV1(B1_N) Y=OAI21(A1,A2,BN) ;
$o22ai_{1,2,4} input(A1,A2,B1,B2) output(Y) Y=OAI22(A1,A2,B1,B2) ;
$o2bb2ai_{1,2,4} input(A1_N,A2_N,B1,B2) output(Y) T=NAND2(A1_N,A2_N) Y=OAI21(B1,B2,T) ;
$o211ai_{1,2,4} input(A1,A2,B1,C1) output(Y) Y=OAI211(A1,A2,B1,C1) ;
$o221ai_{1,2,4} input(A1,A2,B1,B2,C1) output(Y) T=OA22(A1,A2,B1,B2) Y=NAND2(T,C1) ;
$o31ai_{1,2,4} input(A1,A2,A3,B1) output(Y) T=OR3(A1,A2,A3) Y=NAND2(T,B1) ;
$o311ai_{0,1,2,4} input(A1,A2,A3,B1,C1) output(Y) T=OR2(A1,A2) Y=OAI211(T,A3,B1,C1) ;
$o32ai_{1,2,4} input(A1,A2,A3,B1,B2) output(Y) T=OR3(A1,A2,A3) Y=OAI21(B1,B2,T) ;
$o41ai_{1,2,4} input(A1,A2,A3,A4,B1) output(Y) T=OR4(A1,A2,A3,A4) Y=NAND2(T,B1) ;
$o2111ai_{1,2,4} input(A1,A2,B1,C1,D1) output(Y) T=OA211(A1,A2,B1,C1) Y=NAND2(T,D1) ;
$dfxtp_{1,2,4} input(CLK,D) output(Q) Q=DFF(D,CLK) ;
$dfxbp_{1,2} input(CLK,D) output(Q,Q_N) Q=DFF(D,CLK) Q_N=INV1(Q) ;
$dfrtp_{1,2,4} input(CLK,D,RESET_B) output(Q) DR=AND2(D,RESET_B) Q=DFF(DR,CLK) ;
$dfrbp_{1,2} input(CLK,D,RESET_B) output(Q,Q_N) DR=AND2(D,RESET_B) Q=DFF(DR,CLK) Q_N=INV1(Q) ;
$dfstp_{1,2,4} input(CLK,D,SET_B) output(Q) S=INV1(SET_B) DS=OR2(D,S) Q=DFF(DS,CLK) ;
$dfsbp_{1,2} input(CLK,D,SET_B) output(Q,Q_N) S=INV1(SET_B) DS=OR2(D,S) Q=DFF(DS,CLK) Q_N=INV1(Q) ;
$dfbbp_1 input(CLK,D,SET_B,RESET_B) output(Q,Q_N) DR=AND2(D,RESET_B) S=INV1(SET_B) DRS=OR2(DR,S) Q=DFF(DRS,CLK) Q_N=INV1(Q) ;
$dfrtn_1 input(CLK_N,D,RESET_B) output(Q) CLKN=INV1(CLK_N) DR=AND2(D,RESET_B) Q=DFF(DR,CLKN) ;
$dfbbn_{1,2} input(CLK_N,D,SET_B,RESET_B) output(Q,Q_N) CLKN=INV1(CLK_N) DR=AND2(D,RESET_B) S=INV1(SET_B) DRS=OR2(DR,S) Q=DFF(DRS,CLKN) Q_N=INV1(Q) ;
$sdfxtp_{1,2,4} input(CLK,D,SCD,SCE) output(Q) DI=MUX21(D,SCD,SCE) Q=DFF(DI,CLK) ;
$sdfxbp_{1,2} input(CLK,D,SCD,SCE) output(Q,Q_N) DI=MUX21(D,SCD,SCE) Q=DFF(DI,CLK) Q_N=INV1(Q) ;
$sdfrtp_{1,2,4} input(CLK,D,SCD,SCE,RESET_B) output(Q) DR=AND2(D,RESET_B) DI=MUX21(DR,SCD,SCE) Q=DFF(DI,CLK) ;
$sdfrbp_{1,2} input(CLK,D,SCD,SCE,RESET_B) output(Q,Q_N) DR=AND2(D,RESET_B) DI=MUX21(DR,SCD,SCE) Q=DFF(DI,CLK) Q_N=INV1(Q) ;
$sdfstp_{1,2,4} input(CLK,D,SCD,SCE,SET_B) output(Q) S=INV1(SET_B) DS=OR2(D,S) DI=MUX21(DS,SCD,SCE) Q=DFF(DI,CLK) ;
$sdfsbp_{1,2} input(CLK,D,SCD,SCE,SET_B) output(Q,Q_N) S=INV1(SET_B) DS=OR2(D,S) DI=MUX21(DS,SCD,SCE) Q=DFF(DI,CLK) Q_N=INV1(Q) ;
$sdfbbp_1 input(CLK,D,SCD,SCE,SET_B,RESET_B) output(Q,Q_N) DR=AND2(D,RESET_B) S=INV1(SET_B) DRS=OR2(DR,S) DI=MUX21(DRS,SCD,SCE) Q=DFF(DI,CLK) Q_N=INV1(Q) ;
$sdfrtn_1 input(CLK_N,D,SCD,SCE,RESET_B) output(Q) CLKN=INV1(CLK_N) DR=AND2(D,RESET_B) DI=MUX21(DR,SCD,SCE) Q=DFF(DI,CLKN) ;
$sdfbbn_{1,2} input(CLK_N,D,SCD,SCE,SET_B,RESET_B) output(Q,Q_N) CLKN=INV1(CLK_N) DR=AND2(D,RESET_B) S=INV1(SET_B) DRS=OR2(DR,S) DI=MUX21(DRS,SCD,SCE) Q=DFF(DI,CLKN) Q_N=INV1(Q) ;
$edfxtp_1 input(CLK,D,DE) output(Q) GCK=AND2(CLK,DE) Q=DFF(D,GCK) ;
$edfxbp_1 input(CLK,D,DE) output(Q,Q_N) GCK=AND2(CLK,DE) Q=DFF(D,GCK) Q_N=INV1(Q) ;
$sedfxtp_{1,2,4} input(CLK,D,DE,SCD,SCE) output(Q) DI=MUX21(D,SCD,SCE) GCK=AND2(CLK,DE) Q=DFF(DI,GCK) ;
$sedfxbp_{1,2} input(CLK,D,DE,SCD,SCE) output(Q,Q_N) DI=MUX21(D,SCD,SCE) GCK=AND2(CLK,DE) Q=DFF(DI,GCK) Q_N=INV1(Q) ;
$dlxtp_1 input(D,GATE) output(Q) Q=LATCH(D,GATE) ;
$dlxtn_{1,2,4} input(D,GATE_N) output(Q) GN=INV1(GATE_N) Q=LATCH(D,GN) ;
$dlxbp_1 input(D,GATE) output(Q,Q_N) Q=LATCH(D,GATE) Q_N=INV1(Q) ;
$dlxbn_{1,2} input(D,GATE_N) output(Q,Q_N) GN=INV1(GATE_N) Q=LATCH(D,GN) Q_N=INV1(Q) ;
$dlrtp_{1,2,4} input(D,GATE,RESET_B) output(Q) DR=AND2(D,RESET_B) Q=LATCH(DR,GATE) ;
$dlrtn_{1,2,4} input(D,GATE_N,RESET_B) output(Q) GN=INV1(GATE_N) DR=AND2(D,RESET_B) Q=LATCH(DR,GN) ;
$dlrbp_{1,2} input(D,GATE,RESET_B) output(Q,Q_N) DR=AND2(D,RESET_B) Q=LATCH(DR,GATE) Q_N=INV1(Q) ;
$dlrbn_{1,2} input(D,GATE_N,RESET_B) output(Q,Q_N) GN=INV1(GATE_N) DR=AND2(D,RESET_B) Q=LATCH(DR,GN) Q_N=INV1(Q) ;
$dlclkp_{1,2,4} input(CLK,GATE) output(GCLK) GCLK=AND2(CLK,GATE) ;
$sdlclkp_{1,2,4} input(CLK,GATE,SCE) output(GCLK) G=OR2(GATE,SCE) GCLK=AND2(CLK,G) ;
$ebufn_{1,2,4,8} input(A,TE_B) output(Z) TE=INV1(TE_B) Z=AND2(A,TE) ;
$einvn_{0,1,2,4,8} input(A,TE_B) output(Z) TE=INV1(TE_B) ZI=INV1(A) Z=AND2(ZI,TE) ;
$einvp_{1,2,4,8} input(A,TE) output(Z) ZI=INV1(A) Z=AND2(ZI,TE) ;
$lpflow_inputiso0n_1 input(A,SLEEP_B) output(X) X=AND2(A,SLEEP_B) ;
$lpflow_inputiso0p_1 input(A,SLEEP) output(X) SB=INV1(SLEEP) X=AND2(A,SB) ;
$lpflow_inputiso1n_1 input(A,SLEEP_B) output(X) X=AND2(A,SLEEP_B) ;
$lpflow_inputiso1p_1 input(A,SLEEP) output(X) SB=INV1(SLEEP) X=AND2(A,SB) ;
$lpflow_isobufsrc_{1,2,4,8,16} input(A,SLEEP) output(X) AI=INV1(A) X=OR2(AI,SLEEP) ;
$lpflow_isobufsrckapwr_16 input(A,SLEEP) output(X) AI=INV1(A) X=OR2(AI,SLEEP) ;
$lpflow_inputisolatch_1 input(D,SLEEP_B) output(Q) Q=LATCH(D,SLEEP_B) ;
""".replace('$','sky130_fd_sc_hd__'))
"""SkyWater 130nm High Density Digital Standard Cells (skywater-pdk-libs-sky130_fd_sc_hd).
"""
techlib_by_name = {k: v for k, v in globals().items() if isinstance(v, TechLib)}

218
src/kyupy/vcd.py

@ -1,218 +0,0 @@ @@ -1,218 +0,0 @@
"""A simple parser for Verilog Change Dump (VCD) files.
This parser loads the changes into a ndarray of logic values.
Axis 0 is the time step, axis 1 is the variable data.
All variable values are flattened. Offsets are stored in each variable metadata.
"""
from collections import namedtuple
from dataclasses import dataclass, field
from lark import Lark, Transformer
import numpy as np
from . import readtext, logic
@dataclass
class Var:
type: str
width: int
idcode: str
reference: str
scope: 'Scope'
locs: list[int] = field(default_factory=list)
class Scope:
def __init__(self, parent, name='', type='module') -> None:
self.name = name
self.type = type
self.parent_scope = parent
self.sub_scopes : list[Scope] = []
self.vars : list = []
@property
def path(self) -> str:
parts = []
s = self
while s is not None and s.name:
parts.append(s.name)
s = s.parent_scope
return '/'.join(reversed(parts))
def __repr__(self) -> str:
return self.path
class VcdHeader:
def __init__(self) -> None:
self.comment: str = ''
self.date: str = ''
self.timescale: str = ''
self.version: str = ''
self.root_scope = Scope(None)
self.current_scope = self.root_scope
def scope(self, name, type):
new_scope = Scope(self.current_scope, name, type)
self.current_scope.sub_scopes.append(new_scope)
self.current_scope = new_scope
def upscope(self):
assert self.current_scope.parent_scope is not None, "root scope has no parent"
self.current_scope = self.current_scope.parent_scope
def add_var(self, type: str, width: int, idcode: str, reference: str):
self.current_scope.vars.append(Var(type, width, idcode, reference, self.current_scope))
def __repr__(self):
lines = [
'VcdHeader(',
f' comment = {self.comment!r}',
f' date = {self.date!r}',
f' timescale = {self.timescale!r}',
f' version = {self.version!r}',
' scopes:',
]
def fmt_scope(scope, indent=4):
lines.append(f'{" " * (indent // 2)}{scope.type}:{scope.name} ({len(scope.vars)} vars)')
for sub in scope.sub_scopes:
fmt_scope(sub, indent + 2)
for sub in self.root_scope.sub_scopes:
fmt_scope(sub)
lines.append(')')
return '\n'.join(lines)
class VcdVarMap:
def __init__(self, header: VcdHeader, var_locs = lambda _: []) -> None:
self.var_list: list[Var] = []
def collect(scope):
for v in scope.vars:
v.locs = var_locs(v)
if v.locs is not None and len(v.locs) > 0:
assert len(v.locs) == v.width, f'{v.reference}: len(locs)={len(v.locs)} != width={v.width}'
self.var_list.append(v)
for sub in scope.sub_scopes:
collect(sub)
collect(header.root_scope)
self.total_width = max((max(v.locs) for v in self.var_list), default=-1) + 1
self.idcode2var = {var.idcode: var for var in self.var_list}
class VcdData:
def __init__(self, var_map: VcdVarMap, steps, data):
self.var_map = var_map
self.steps = steps
self.data = data
def __repr__(self) -> str:
return f'{{variables: {len(self.var_map.var_list)}, bits: {self.data.shape[0]}, steps: {self.data.shape[1]}}}'
class VcdHeaderTransformer(Transformer):
def __init__(self):
super().__init__()
self.header = VcdHeader()
def comment(self, args): self.header.comment = args[0].value.strip()
def date(self, args): self.header.date = args[0].value.strip()
def timescale(self, args): self.header.timescale = args[0].value.strip()
def version(self, args): self.header.version = args[0].value.strip()
def scope(self, args):
type, name = args
self.header.scope(name.value, type.value)
def upscope(self, args):
self.header.upscope()
def var(self, args):
type, width, idcode, reference = args
reference = reference.value.strip()
width = int(width)
self.header.add_var(type.value, width, idcode.value, reference)
def start(self, args):
return self.header
#return tuple(args) if len(args) > 1 else (args[0], None)
GRAMMAR = r"""
start: (_declaration_command)*
_declaration_command: ( comment | date | scope | timescale | upscope | var | version ) "$end"
comment: "$comment" TEXT?
date: "$date" TEXT
scope: "$scope" /[^\s]+/ /[^\s]+/
timescale: "$timescale" TEXT
upscope: "$upscope"
var: "$var" /[^\s]+/ /[^\s]+/ /[^\s]+/ TEXT
version: "$version" TEXT
TEXT: /(?:(?!\$end)[\s\S])+/
%ignore ( /\r?\n/ )+
%ignore /[\t \f]+/
"""
def load(file, var_locs = lambda _: [], step_filter = lambda *_: True, s_len = None) -> VcdData:
"""Parses the contents of ``file`` as Verilog Change Dump (VCD).
:param file: A file name or a file handle. Files with `.gz`-suffix are decompressed on-the-fly.
:param var_locs: A callback ``(var) -> list[int]`` mapping each variable to ndarray column indices. Empty list drops the variable.
:param step_filter: A callback ``(time, values, var_map) -> bool`` to select which timesteps to include.
:param s_len: length of the first axis of returned pattern data.
:return: A VcdData object with metadata and an ndarray with all values.
"""
vcd = readtext(file)
header_size = vcd.find('$enddefinitions')
assert header_size > 0, "invalid VCD file: end of header not found"
vcd_header_str = vcd[:header_size]
vcd_header : VcdHeader = Lark(GRAMMAR, parser="lalr", lexer='contextual', transformer=VcdHeaderTransformer()).parse(vcd_header_str) # type: ignore
vcd_data = vcd[header_size:].splitlines()
var_map = VcdVarMap(vcd_header, var_locs)
width = s_len if s_len is not None else var_map.total_width
chunk_size = 10240
chunks = []
chunk = np.full((chunk_size, width), logic.UNASSIGNED, dtype=np.uint8)
_val_map = {'0': logic.ZERO, '1': logic.ONE,
'x': logic.UNKNOWN, 'X': logic.UNKNOWN,
'z': logic.UNASSIGNED, 'Z': logic.UNASSIGNED}
_pad_char = {'0': '0', '1': '0', 'x': 'x', 'X': 'x', 'z': 'z', 'Z': 'z'}
current_time = None
steps = []
step_idx = 0
for line in vcd_data:
if not line or line[0] == '$':
continue
if line[0] == '#':
if step_filter(current_time, chunk[step_idx], var_map):
step_idx += 1
steps.append(current_time)
if step_idx >= chunk_size:
chunks.append(chunk)
chunk = np.empty((chunk_size, width), dtype=np.uint8)
chunk[0] = chunks[-1][-1]
step_idx = 0
else:
chunk[step_idx] = chunk[step_idx - 1]
current_time = int(line[1:])
continue
if line[0] in ('b', 'B'):
value_str, idcode = line[1:].split(None, 1)
else:
value_str, idcode = line[0], line[1:]
var = var_map.idcode2var.get(idcode)
if var is None:
continue
if len(value_str) < var.width:
value_str = _pad_char.get(value_str[0], '0') * (var.width - len(value_str)) + value_str
for i, c in enumerate(value_str):
if var.locs[i] >= 0:
chunk[step_idx, var.locs[i]] = _val_map.get(c, logic.UNKNOWN)
chunks.append(chunk[:step_idx])
data = np.concatenate(chunks, axis=0).T
return VcdData(var_map, steps, data)

15
src/kyupy/verilog.py

@ -52,14 +52,13 @@ class VerilogTransformer(Transformer): @@ -52,14 +52,13 @@ class VerilogTransformer(Transformer):
@staticmethod
def instantiation(args):
pinmap = {}
if args[2] is not None:
for idx, pin in enumerate(args[2:]):
p = pin.children[0]
if isinstance(p, tuple): # named pin
if p[1] is not None:
pinmap[p[0]] = p[1]
else: # unnamed pin
pinmap[idx] = p
for idx, pin in enumerate(args[2:]):
p = pin.children[0]
if isinstance(p, tuple): # named pin
if p[1] is not None:
pinmap[p[0]] = p[1]
else: # unnamed pin
pinmap[idx] = p
return Instantiation(args[0], args[1], pinmap)
@staticmethod

2
src/kyupy/wave_sim.py

@ -434,7 +434,7 @@ class WaveSimCuda(WaveSim): @@ -434,7 +434,7 @@ class WaveSimCuda(WaveSim):
grid_dim = self._grid_dim(self.sims, self.s_len)
wave_assign_gpu[grid_dim, self._block_dim](self.c, self.s, self.c_locs, self.ppi_offset)
def _grid_dim(self, x, y): return cdiv(int(x), self._block_dim[0]), cdiv(int(y), self._block_dim[1])
def _grid_dim(self, x, y): return cdiv(x, self._block_dim[0]), cdiv(y, self._block_dim[1])
def c_prop(self, sims=None, seed=1, op_from=0, op_to=None, delta=0):
sims = min(sims or self.sims, self.sims)

29
tests/test_atalanta.py

@ -1,29 +0,0 @@ @@ -1,29 +0,0 @@
import numpy as np
from kyupy import atalanta, logic
def test_parse():
text = '\n'.join([
'* Test pattern file',
'1: 0011',
'2: 1100',
'1010',
])
tf = atalanta.parse(text)
# second-to-last axis = inputs, last axis = patterns
assert tf.patterns.shape == (4, 3)
assert tf.patterns.dtype == np.uint8
assert set(np.unique(tf.patterns)) <= {logic.ZERO, logic.ONE}
# patterns are stored along the last axis
assert list(tf.patterns[:, 0]) == [logic.ZERO, logic.ZERO, logic.ONE, logic.ONE]
assert list(tf.patterns[:, 1]) == [logic.ONE, logic.ONE, logic.ZERO, logic.ZERO]
assert list(tf.patterns[:, 2]) == [logic.ONE, logic.ZERO, logic.ONE, logic.ZERO]
def test_empty():
tf = atalanta.parse('* only comments\n* nothing else\n')
assert tf.patterns.shape == (0, 0)
assert tf.patterns.dtype == np.uint8

6
tests/test_circuit.py

@ -2,7 +2,7 @@ import pickle @@ -2,7 +2,7 @@ import pickle
from kyupy.circuit import GrowingList, Circuit, Node, Line
from kyupy import verilog, bench
from kyupy.techlib import KYUPY, SAED32
from kyupy.techlib import SAED32
def test_growing_list():
gl = GrowingList()
@ -155,7 +155,7 @@ def test_substitute(): @@ -155,7 +155,7 @@ def test_substitute():
def test_resolve(mydir):
c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32)
s_names = [n.name for n in c.s_nodes(SAED32)]
s_names = [n.name for n in c.s_nodes]
c.resolve_tlib_cells(SAED32)
s_names_prim = [n.name for n in c.s_nodes(KYUPY)]
s_names_prim = [n.name for n in c.s_nodes]
assert s_names == s_names_prim, 'resolve_tlib_cells does not preserve names or order of s_nodes'

103
tests/test_logic_sim.py

@ -2,8 +2,8 @@ import numpy as np @@ -2,8 +2,8 @@ import numpy as np
from kyupy.logic_sim import LogicSim, LogicSim2V, LogicSim4V, LogicSim6V
from kyupy import bench, logic, sim, verilog
from kyupy.logic import mv_str, mvarray, bparray, bp_to_mv, mv_to_bp
from kyupy.techlib import SAED90, KYUPY
from kyupy.logic import mvarray, bparray, bp_to_mv, mv_to_bp
from kyupy.techlib import SAED90
def test_dangling():
c = verilog.parse('''
@ -20,18 +20,6 @@ def test_dangling(): @@ -20,18 +20,6 @@ def test_dangling():
sim.simulate(p)
assert p[1,0] == logic.ONE
def test_used_output():
c = bench.parse('input(i0) output(o0) output(o1) o0=BUF1(i0) o1=BUF(o0)')
sim = LogicSim2V(c)
expect = mvarray('000', '111')
actual = sim.simulate(expect.copy())
np.testing.assert_array_equal(
expect[c.io_locs('o0')],
actual[c.io_locs('o0')])
np.testing.assert_array_equal(
expect[c.io_locs('o1')],
actual[c.io_locs('o1')])
def assert_equal_shape_and_contents(actual, desired):
desired = np.array(desired, dtype=np.uint8)
assert actual.shape == desired.shape
@ -117,7 +105,7 @@ def test_LogicSim6V_simprims(mydir): @@ -117,7 +105,7 @@ def test_LogicSim6V_simprims(mydir):
sim1.simulate(tests1)
sim2.simulate(tests2)
for loc1, loc2 in zip(c1.io_locs('o'), c2.io_locs('o')):
n = c1.s_nodes(KYUPY)[loc1]
n = c1.s_nodes[loc1]
if (tests1[loc1] != tests2[loc2]).any():
print(f'Mismatch at output {n}')
np.testing.assert_array_equal(
@ -162,12 +150,14 @@ def test_2v(): @@ -162,12 +150,14 @@ def test_2v():
o31=OAI211(i0,i1,i2,i3)
o32=MUX21(i0,i1,i2)
''')
s = LogicSim2V(c, 16)
s.s_assign[...] = logic.mvarray([f'{i:04b}'+('-'*(s.s_len-4)) for i in range(16)])
s = LogicSim(c, 16, m=2)
bpa = logic.bparray([f'{i:04b}'+('-'*(s.s_len-4)) for i in range(16)])
s.s[0] = bpa
s.s_to_c()
s.c_prop()
s.c_to_s()
for res, exp in zip(logic.packbits(s.s_result[4:], dtype=np.uint32), [
mva = logic.bp_to_mv(s.s[1])
for res, exp in zip(logic.packbits(mva[4:], dtype=np.uint32), [
sim.BUF1, sim.INV1,
sim.AND2, sim.AND3, sim.AND4,
sim.NAND2, sim.NAND3, sim.NAND4,
@ -188,17 +178,19 @@ def test_2v(): @@ -188,17 +178,19 @@ def test_2v():
def test_4v():
c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')
s = LogicSim4V(c, 16)
s = LogicSim(c, 16, m=4)
assert s.s_len == 5
s.s_assign[...] = mvarray(
bpa = bparray(
'00---', '01---', '0----', '0X---',
'10---', '11---', '1----', '1X---',
'-0---', '-1---', '-----', '-X---',
'X0---', 'X1---', 'X----', 'XX---')
s.s[0] = bpa
s.s_to_c()
s.c_prop()
s.c_to_s()
assert_equal_shape_and_contents(s.s_result, mvarray(
mva = bp_to_mv(s.s[1])
assert_equal_shape_and_contents(mva, mvarray(
'--001', '--011', '--0X1', '--0X1',
'--010', '--110', '--X10', '--X10',
'--0XX', '--X1X', '--XXX', '--XXX',
@ -206,34 +198,38 @@ def test_4v(): @@ -206,34 +198,38 @@ def test_4v():
def test_4v_fault():
c = bench.parse('input(x, y) output(a) a=and(x,y)')
s = LogicSim4V(c, 16)
s = LogicSim(c, 16, m=4)
assert s.s_len == 3
s.s_assign[...] = mvarray(
bpa = bparray(
'00-', '01-', '0--', '0X-',
'10-', '11-', '1--', '1X-',
'-0-', '-1-', '---', '-X-',
'X0-', 'X1-', 'X--', 'XX-')
s.s[0] = bpa
s.s_to_c()
s.c_prop()
s.c_to_s()
assert_equal_shape_and_contents(s.s_result, mvarray(
mva = bp_to_mv(s.s[1])
assert_equal_shape_and_contents(mva, mvarray(
'--0', '--0', '--0', '--0',
'--0', '--1', '--X', '--X',
'--0', '--X', '--X', '--X',
'--0', '--X', '--X', '--X'))
fault_line = s.circuit.cells['a'].ins[0]
s.s_to_c()
s.c_prop(fault_line=fault_line.index, fault_model=1)
s.c_prop(fault_line=fault_line, fault_model=1)
s.c_to_s()
assert_equal_shape_and_contents(s.s_result, mvarray(
mva = bp_to_mv(s.s[1])
assert_equal_shape_and_contents(mva, mvarray(
'--0', '--1', '--X', '--X',
'--0', '--1', '--X', '--X',
'--0', '--1', '--X', '--X',
'--0', '--1', '--X', '--X'))
s.s_to_c()
s.c_prop(fault_line=fault_line.index, fault_model=0)
s.c_prop(fault_line=fault_line, fault_model=0)
s.c_to_s()
assert_equal_shape_and_contents(s.s_result, mvarray(
mva = bp_to_mv(s.s[1])
assert_equal_shape_and_contents(mva, mvarray(
'--0', '--0', '--0', '--0',
'--0', '--0', '--0', '--0',
'--0', '--0', '--0', '--0',
@ -259,11 +255,11 @@ def test_6v(): @@ -259,11 +255,11 @@ def test_6v():
resp = s.s[1].copy()
exp_resp = np.copy(mva)
exp_resp[:2] = logic.UNASSIGNED
exp_resp[:2] = logic.ZERO
np.testing.assert_allclose(resp, exp_resp)
def xtest_8v():
def test_8v():
c = bench.parse('input(x, y) output(a, o, n, xo) a=and(x,y) o=or(x,y) n=not(x) xo=xor(x,y)')
s = LogicSim(c, 64, m=8)
assert s.s_len == 6
@ -290,8 +286,8 @@ def xtest_8v(): @@ -290,8 +286,8 @@ def xtest_8v():
np.testing.assert_allclose(resp, exp_resp)
def xtest_loop():
c = bench.parse('q=DFF(d) d=NOT(q)')
def test_loop():
c = bench.parse('q=dff(d) d=not(q)')
s = LogicSim(c, 4, m=8)
assert s.s_len == 1
mva = mvarray([['0'], ['1'], ['R'], ['F']])
@ -317,8 +313,8 @@ def xtest_loop(): @@ -317,8 +313,8 @@ def xtest_loop():
# assert resp[3] == 'F'
def xtest_latch():
c = bench.parse('input(d, t) output(q) q=LATCH(d, t)')
def test_latch():
c = bench.parse('input(d, t) output(q) q=latch(d, t)')
s = LogicSim(c, 8, m=8)
assert s.s_len == 4
mva = mvarray('00-0', '00-1', '01-0', '01-1', '10-0', '10-1', '11-0', '11-1')
@ -331,6 +327,19 @@ def xtest_latch(): @@ -331,6 +327,19 @@ def xtest_latch():
# assert resp[i] == exp[i]
def test_b01(mydir):
c = bench.load(mydir / 'b01.bench')
# 8-valued
s = LogicSim(c, 8, m=8)
mva = np.zeros((s.s_len, 8), dtype=np.uint8)
s.s[0] = mv_to_bp(mva)
s.s_to_c()
s.c_prop()
s.c_to_s()
bp_to_mv(s.s[1])
def sim_and_compare(c, test_resp, m=8):
tests, resp = test_resp
lsim = LogicSim(c, m=m, sims=tests.shape[1])
@ -347,14 +356,14 @@ def sim_and_compare(c, test_resp, m=8): @@ -347,14 +356,14 @@ def sim_and_compare(c, test_resp, m=8):
print(f'mismatch pattern:{pat} ppio:{idx} exp:{logic.mv_str(resp[idx,pat])} act:{logic.mv_str(resp_sim[idx,pat])}')
assert len(idxs) == 0
def sim_and_compare_cls(c, test_resp, SimCls):
def sim_and_compare_6v(c, test_resp):
tests, resp = test_resp
lsim = SimCls(c, sims=tests.shape[1])
lsim.s_assign[...] = tests
lsim = LogicSim6V(c, sims=tests.shape[1])
lsim.s[0] = tests
lsim.s_to_c()
lsim.c_prop()
lsim.c_to_s()
resp_sim = lsim.s_result
resp_sim = lsim.s[1]
idxs, pats = np.nonzero(((resp == logic.ONE) & (resp_sim != logic.ONE)) | ((resp == logic.ZERO) & (resp_sim != logic.ZERO)))
for i, (idx, pat) in enumerate(zip(idxs, pats)):
if i >= 10:
@ -365,32 +374,28 @@ def sim_and_compare_cls(c, test_resp, SimCls): @@ -365,32 +374,28 @@ def sim_and_compare_cls(c, test_resp, SimCls):
def test_b15_2ig_sa_2v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim2V)
sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=2)
def test_b15_2ig_sa_4v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim4V)
sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=4)
def test_b15_2ig_sa_6v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim6V)
sim_and_compare_6v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp)
def xtest_b15_2ig_sa_8v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
def test_b15_2ig_sa_8v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=8)
def test_b15_4ig_sa_2v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim2V)
sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=2)
def test_b15_4ig_sa_4v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim4V)
def test_b15_4ig_sa_6v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim6V)
sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=4)
def xtest_b15_4ig_sa_8v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
def test_b15_4ig_sa_8v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=8)

2
tests/test_stil.py

@ -14,7 +14,7 @@ def test_b15(mydir): @@ -14,7 +14,7 @@ def test_b15(mydir):
assert len(resp) > 0
s2 = stil.load(mydir / 'b15_2ig.tf_nf.stil.gz')
tests = s2.tests_loc(b15, SAED32)
tests = s2.tests_loc(b15)
resp = s2.responses(b15)
assert len(tests) > 0
assert len(resp) > 0

8
tests/test_verilog.py

@ -9,7 +9,7 @@ def test_b01(mydir): @@ -9,7 +9,7 @@ def test_b01(mydir):
assert len(c.nodes) == 139
assert len(c.lines) == 203
stats = c.stats(SAED90)
stats = c.stats
assert stats['input'] == 6
assert stats['output'] == 3
assert stats['__seq__'] == 5
@ -19,7 +19,7 @@ def test_b15(mydir): @@ -19,7 +19,7 @@ def test_b15(mydir):
c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32)
assert len(c.nodes) == 12067
assert len(c.lines) == 20731
stats = c.stats(SAED32)
stats = c.stats
assert stats['input'] == 40
assert stats['output'] == 71
assert stats['__seq__'] == 417
@ -29,7 +29,7 @@ def test_gates(mydir): @@ -29,7 +29,7 @@ def test_gates(mydir):
c = verilog.load(mydir / 'gates.v', tlib=NANGATE45)
assert len(c.nodes) == 18
assert len(c.lines) == 21
stats = c.stats(NANGATE45)
stats = c.stats
assert stats['input'] == 3
assert stats['output'] == 4
assert stats['__seq__'] == 0
@ -39,7 +39,7 @@ def test_halton2(mydir): @@ -39,7 +39,7 @@ def test_halton2(mydir):
c = verilog.load(mydir / 'rng_haltonBase2.synth_yosys.v', tlib=SAED90)
assert len(c.nodes) == 146
assert len(c.lines) == 210
stats = c.stats(SAED90)
stats = c.stats
assert stats['input'] == 2
assert stats['output'] == 12
assert stats['__seq__'] == 12

8
tests/test_wave_sim.py

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import numpy as np
from kyupy.wave_sim import WaveSim, WaveSimCuda, wave_eval_cpu, TMIN, TMAX
from kyupy.logic_sim import LogicSim6V
from kyupy.logic_sim import LogicSim
from kyupy import logic, bench, sim
from kyupy.logic import mvarray
@ -175,12 +175,12 @@ def compare_to_logic_sim(wsim: WaveSim): @@ -175,12 +175,12 @@ def compare_to_logic_sim(wsim: WaveSim):
resp |= ((resp ^ (resp >> 1)) & 1) << 2 # transitions
resp[wsim.pi_s_locs] = logic.UNASSIGNED
lsim = LogicSim6V(wsim.circuit, tests.shape[-1])
lsim.s_assign[...] = tests
lsim = LogicSim(wsim.circuit, tests.shape[-1])
lsim.s[0] = logic.mv_to_bp(tests)
lsim.s_to_c()
lsim.c_prop()
lsim.c_to_s()
exp = lsim.s_result[:,:tests.shape[-1]]
exp = logic.bp_to_mv(lsim.s[1])[:,:tests.shape[-1]]
resp[resp == logic.PPULSE] = logic.ZERO
resp[resp == logic.NPULSE] = logic.ONE

Loading…
Cancel
Save