Compare commits
21
Commits
f863b70457
..
devel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea2ef1c3ec | ||
|
|
aabef17eea | ||
|
|
f0c10633bf | ||
|
|
ed9b5c6cd6 | ||
|
|
e19463d249 | ||
|
|
7a69ce9016 | ||
|
|
f5af7ec3d9 | ||
|
|
53309f9e59 | ||
|
|
c90bc89522 | ||
|
|
bda89dc1b0 | ||
|
|
449bf9a031 | ||
|
|
df463826ae | ||
|
|
aebdf9540d | ||
|
|
9dc58f8c40 | ||
|
|
e8a9025c56 | ||
|
|
8aa0f09254 | ||
|
|
4f7e605819 | ||
|
|
c1ea05555e | ||
|
|
e7b5567d87 | ||
|
|
3a2cfb7788 | ||
|
|
ca25942b27 |
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
build:
|
build:
|
||||||
os: "ubuntu-20.04"
|
os: "ubuntu-24.04"
|
||||||
tools:
|
tools:
|
||||||
python: "3.12"
|
python: "3.12"
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
+3
-1
@@ -33,8 +33,10 @@ build-backend = "hatchling.build"
|
|||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"myst-parser>=4.0.1",
|
"myst-parser>=4.0.1",
|
||||||
"pytest>=9.0.1",
|
|
||||||
"sphinx>=8.1.3",
|
"sphinx>=8.1.3",
|
||||||
"sphinx-rtd-theme>=3.0.2",
|
"sphinx-rtd-theme>=3.0.2",
|
||||||
|
"pytest>=9.0.1",
|
||||||
|
"ipython>=9.14.1",
|
||||||
|
"jupyter>=1.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
+30
-18
@@ -12,6 +12,8 @@ import gzip
|
|||||||
|
|
||||||
import numpy as np
|
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)])
|
_pop_count_lut = np.asarray([bin(x).count('1') for x in range(256)])
|
||||||
|
|
||||||
@@ -111,9 +113,9 @@ class Timer:
|
|||||||
|
|
||||||
|
|
||||||
class Timers:
|
class Timers:
|
||||||
def __init__(self, t={}): self.timers = defaultdict(Timer) | t
|
def __init__(self, t={}): self.timers: dict[str, Timer] = defaultdict(Timer) | t
|
||||||
def __getitem__(self, name): return self.timers[name]
|
def __getitem__(self, name): return self.timers[name]
|
||||||
def __repr__(self): return '{' + ', '.join([f'{k}: {v}' for k, v in self.timers.items()]) + '}'
|
def __repr__(self): return '{' + ', '.join([f'{k}: {v:.2f}' for k, v in self.dict().items()]) + '}'
|
||||||
def __add__(self, t):
|
def __add__(self, t):
|
||||||
tmr = Timers(self.timers)
|
tmr = Timers(self.timers)
|
||||||
for k, v in t.timers.items(): tmr.timers[k] += v
|
for k, v in t.timers.items(): tmr.timers[k] += v
|
||||||
@@ -121,7 +123,7 @@ class Timers:
|
|||||||
def sum(self):
|
def sum(self):
|
||||||
return sum([v.s for v in self.timers.values()])
|
return sum([v.s for v in self.timers.values()])
|
||||||
def dict(self):
|
def dict(self):
|
||||||
return dict([(k, v.s) for k, v in self.timers.items()])
|
return dict(sorted([(k, v.s) for k, v in self.timers.items()], key=lambda item: -item[1]))
|
||||||
|
|
||||||
|
|
||||||
class Log:
|
class Log:
|
||||||
@@ -198,25 +200,35 @@ class Log:
|
|||||||
"""Log an error message."""
|
"""Log an error message."""
|
||||||
self.log('E', 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):
|
def range(self, *args):
|
||||||
"""A generator that operates just like the ``range()`` built-in, and also occasionally logs the progress
|
"""A generator that operates just like the ``range()`` built-in, and also occasionally logs the progress
|
||||||
and compute time estimates."""
|
and compute time estimates."""
|
||||||
elems = len(range(*args))
|
elems = len(range(*args))
|
||||||
start_time = time.perf_counter()
|
with self.progress() as p:
|
||||||
lastlog_time = start_time
|
for elem, i in enumerate(range(*args)):
|
||||||
log_interval = 5
|
yield i
|
||||||
for elem, i in enumerate(range(*args)):
|
p.update((elem + 1) / elems)
|
||||||
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()
|
log = Log()
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"""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))
|
||||||
+24
-4
@@ -14,7 +14,7 @@ Since simulators only support at most four-input-gates, gates with more than fou
|
|||||||
from lark import Lark, Transformer
|
from lark import Lark, Transformer
|
||||||
|
|
||||||
from .circuit import Circuit, Node, Line
|
from .circuit import Circuit, Node, Line
|
||||||
from . import readtext, batchrange
|
from . import readtext, batchrange, log
|
||||||
|
|
||||||
def treeify(l, max_degree=4):
|
def treeify(l, max_degree=4):
|
||||||
if len(l) <= max_degree: return l
|
if len(l) <= max_degree: return l
|
||||||
@@ -26,12 +26,32 @@ class BenchTransformer(Transformer):
|
|||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.c = Circuit(name)
|
self.c = Circuit(name)
|
||||||
|
self.inputs = set()
|
||||||
|
self.outputs = set()
|
||||||
|
|
||||||
def start(self, _): return self.c
|
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 parameters(self, args): return [self.c.get_or_add_fork(str(name)) for name in args if name is not None]
|
||||||
|
|
||||||
def interface(self, args): self.c.io_nodes.extend(args[0])
|
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 _cell_tree_inner(self, name, kind, inner_kind, drivers):
|
def _cell_tree_inner(self, name, kind, inner_kind, drivers):
|
||||||
cell = Node(self.c, name, f'{kind}{len(drivers)}')
|
cell = Node(self.c, name, f'{kind}{len(drivers)}')
|
||||||
@@ -69,8 +89,8 @@ class BenchTransformer(Transformer):
|
|||||||
GRAMMAR = r"""
|
GRAMMAR = r"""
|
||||||
start: (statement)*
|
start: (statement)*
|
||||||
statement: input | output | assignment
|
statement: input | output | assignment
|
||||||
input: ("INPUT" | "input") parameters -> interface
|
input: ("INPUT" | "input") parameters
|
||||||
output: ("OUTPUT" | "output") parameters -> interface
|
output: ("OUTPUT" | "output") parameters
|
||||||
assignment: NAME "=" NAME parameters
|
assignment: NAME "=" NAME parameters
|
||||||
parameters: "(" [ NAME ( "," NAME )* ] ")"
|
parameters: "(" [ NAME ( "," NAME )* ] ")"
|
||||||
NAME: /[-_a-z0-9]+/i
|
NAME: /[-_a-z0-9]+/i
|
||||||
|
|||||||
+30
-33
@@ -14,10 +14,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections import deque, defaultdict
|
from collections import deque, defaultdict
|
||||||
import re
|
import re
|
||||||
from typing import Union, Any
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from . import NestedNumericList, NestedStrIntDict
|
||||||
|
|
||||||
class GrowingList[T](list[T]):
|
class GrowingList[T](list[T]):
|
||||||
def __setitem__(self, index, value):
|
def __setitem__(self, index, value):
|
||||||
@@ -280,20 +280,19 @@ class Circuit:
|
|||||||
Use the :class:`Node` constructor and :py:attr:`Node.remove()` to add and remove nodes.
|
Use the :class:`Node` constructor and :py:attr:`Node.remove()` to add and remove nodes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
def s_nodes(self, tlib: 'TechLib') -> list: # type: ignore
|
||||||
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).
|
"""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.
|
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.
|
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 'dff' in n.kind.lower()] + [n for n in self.nodes if 'latch' in n.kind.lower()]
|
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)]
|
||||||
|
|
||||||
def io_locs(self, prefix):
|
def io_locs(self, prefix):
|
||||||
"""Returns the indices of primary I/Os that start with given name prefix.
|
"""Returns a list of 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.
|
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 only one I/O cell matches the given prefix, a list with a single integer is returned.
|
||||||
If a bus matches the given prefix, a sorted list of indices 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.
|
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]`).
|
Lists for bus indices are sorted from LSB (e.g. :code:`data[0]`) to MSB (e.g. :code:`data[31]`).
|
||||||
@@ -303,16 +302,16 @@ class Circuit:
|
|||||||
"""
|
"""
|
||||||
return self._locs(prefix, list(self.io_nodes))
|
return self._locs(prefix, list(self.io_nodes))
|
||||||
|
|
||||||
def s_locs(self, prefix):
|
def s_locs(self, prefix, tlib: 'TechLib'): # type: ignore
|
||||||
"""Returns the indices of I/Os and sequential elements that start with given name 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.
|
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.
|
It works the same as :py:attr:`io_locs`. See there for more details.
|
||||||
"""
|
"""
|
||||||
return self._locs(prefix, self.s_nodes)
|
return self._locs(prefix, self.s_nodes(tlib))
|
||||||
|
|
||||||
def _locs(self, prefix, nodes:list[Node]) -> Node|list[Any]: # can return list[list[...]]
|
def _locs(self, prefix, nodes:list[Node]) -> NestedNumericList: # can return list[list[...]]
|
||||||
d_top = dict()
|
d_top: NestedStrIntDict = dict()
|
||||||
for i, n in enumerate(nodes):
|
for i, n in enumerate(nodes):
|
||||||
if m := re.match(fr'({re.escape(prefix)}.*?)((?:[\d_\[\]])*$)', n.name):
|
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]
|
path = [m[1]] + [int(v) for v in re.split(r'[_\[\]]+', m[2]) if len(v) > 0]
|
||||||
@@ -320,16 +319,16 @@ class Circuit:
|
|||||||
for j in path[:-1]:
|
for j in path[:-1]:
|
||||||
d[j] = d.get(j, dict())
|
d[j] = d.get(j, dict())
|
||||||
d = d[j]
|
d = d[j]
|
||||||
|
assert isinstance(d, dict)
|
||||||
d[path[-1]] = i
|
d[path[-1]] = i
|
||||||
|
|
||||||
# sort recursively for multi-dimensional lists.
|
# 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
|
def sorted_values(d) -> NestedNumericList: return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d
|
||||||
l = sorted_values(d_top)
|
l = sorted_values(d_top)
|
||||||
while isinstance(l, list) and len(l) == 1: l = l[0]
|
while isinstance(l, list) and len(l) == 1 and isinstance(l[0], list): l = l[0]
|
||||||
return l #None if isinstance(l, list) and len(l) == 0 else l
|
return l
|
||||||
|
|
||||||
@property
|
def stats(self, tlib: 'TechLib'): # type: ignore
|
||||||
def stats(self):
|
|
||||||
"""A dictionary with the counts of all different elements in the circuit.
|
"""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
|
The dictionary contains the number of all different kinds of nodes, the number
|
||||||
@@ -347,9 +346,9 @@ class Circuit:
|
|||||||
stats['__line__'] = len(self.lines)
|
stats['__line__'] = len(self.lines)
|
||||||
for n in self.cells.values():
|
for n in self.cells.values():
|
||||||
stats[n.kind] += 1
|
stats[n.kind] += 1
|
||||||
if 'dff' in n.kind.lower(): stats['__dff__'] += 1
|
if tlib.is_dff(n.kind): stats['__dff__'] += 1
|
||||||
elif 'latch' in n.kind.lower(): stats['__latch__'] += 1
|
elif tlib.is_latch(n.kind): stats['__latch__'] += 1
|
||||||
elif 'put' not in n.kind.lower(): stats['__comb__'] += 1 # no input or output
|
elif 'put' not in n.kind.lower(): stats['__comb__'] += 1 # don't count input or output towards comb
|
||||||
stats['__seq__'] = stats['__dff__'] + stats['__latch__']
|
stats['__seq__'] = stats['__dff__'] + stats['__latch__']
|
||||||
return dict(stats)
|
return dict(stats)
|
||||||
|
|
||||||
@@ -581,24 +580,23 @@ class Circuit:
|
|||||||
if line is not None:
|
if line is not None:
|
||||||
yield line
|
yield line
|
||||||
|
|
||||||
def reversed_topological_order(self):
|
def reversed_topological_order(self, tlib: 'TechLib'): # type: ignore
|
||||||
"""Generator function to iterate over all nodes in reversed topological order.
|
"""Generator function to iterate over all nodes in reversed topological order.
|
||||||
|
|
||||||
Nodes without output lines and nodes whose :py:attr:`Node.kind` contains the
|
Nodes without output lines and sequential nodes (flip-flops, latches) are yielded first.
|
||||||
substrings 'dff' or 'latch' are yielded first.
|
|
||||||
"""
|
"""
|
||||||
visit_count = [0] * len(self.nodes)
|
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() or 'latch' in n.kind.lower())
|
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))
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
n = queue.popleft()
|
n = queue.popleft()
|
||||||
for line in n.ins:
|
for line in n.ins:
|
||||||
pred = line.driver
|
pred = line.driver
|
||||||
visit_count[pred] += 1
|
visit_count[pred] += 1
|
||||||
if visit_count[pred] == len(pred.outs) and 'dff' not in pred.kind.lower() and 'latch' not in pred.kind.lower():
|
if visit_count[pred] == len(pred.outs) and not tlib.is_dff(pred.kind) and not tlib.is_latch(pred.kind):
|
||||||
queue.append(pred)
|
queue.append(pred)
|
||||||
yield n
|
yield n
|
||||||
|
|
||||||
def fanin(self, origin_nodes):
|
def fanin(self, origin_nodes, tlib: 'TechLib'): # type: ignore
|
||||||
"""Generator function to iterate over the fan-in cone of a given list of 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.
|
Nodes are yielded in reversed topological order.
|
||||||
@@ -606,11 +604,10 @@ class Circuit:
|
|||||||
marks = [False] * len(self.nodes)
|
marks = [False] * len(self.nodes)
|
||||||
for n in origin_nodes:
|
for n in origin_nodes:
|
||||||
marks[n] = True
|
marks[n] = True
|
||||||
for n in self.reversed_topological_order():
|
for n in self.reversed_topological_order(tlib):
|
||||||
if not marks[n]:
|
if not marks[n]:
|
||||||
for line in n.outs:
|
for line in n.outs.without_nones():
|
||||||
if line is not None:
|
marks[n] |= marks[line.reader]
|
||||||
marks[n] |= marks[line.reader]
|
|
||||||
if marks[n]:
|
if marks[n]:
|
||||||
yield n
|
yield n
|
||||||
|
|
||||||
@@ -633,8 +630,8 @@ class Circuit:
|
|||||||
queue.append(succ)
|
queue.append(succ)
|
||||||
yield n
|
yield n
|
||||||
|
|
||||||
def fanout_free_regions(self):
|
def fanout_free_regions(self, tlib: 'TechLib'): # type: ignore
|
||||||
for stem in self.reversed_topological_order():
|
for stem in self.reversed_topological_order(tlib):
|
||||||
if len(stem.outs) == 1 and 'dff' not in stem.kind.lower(): continue
|
if len(stem.outs) == 1 and 'dff' not in stem.kind.lower(): continue
|
||||||
region = []
|
region = []
|
||||||
if 'dff' in stem.kind.lower():
|
if 'dff' in stem.kind.lower():
|
||||||
@@ -654,11 +651,11 @@ class Circuit:
|
|||||||
region.append(n)
|
region.append(n)
|
||||||
yield stem, region
|
yield stem, region
|
||||||
|
|
||||||
def dot(self, format='svg', graph_attr={}, line_labels={}):
|
def dot(self, tlib: 'TechLib', format='svg', graph_attr={}, line_labels={}):
|
||||||
from graphviz import Digraph
|
from graphviz import Digraph
|
||||||
dot = Digraph(format=format, graph_attr={'rankdir': 'LR', 'splines': 'true', 'size': '10', 'ranksep': '0.1'} | graph_attr)
|
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))
|
s_dict = dict((n, i) for i, n in enumerate(self.s_nodes(tlib)))
|
||||||
node_level = np.zeros(len(self.nodes), dtype=np.uint32)
|
node_level = np.zeros(len(self.nodes), dtype=np.uint32)
|
||||||
level_nodes = defaultdict(list)
|
level_nodes = defaultdict(list)
|
||||||
for n, lv in self.topological_order_with_level():
|
for n, lv in self.topological_order_with_level():
|
||||||
@@ -681,7 +678,7 @@ class Circuit:
|
|||||||
reader = f'{l.reader.index}:i{l.reader_pin}' if len(l.reader.ins)>1 else f'{l.reader.index}'
|
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))
|
label = str(line_labels.get(l, l.index))
|
||||||
if node_level[l.driver] == node_level[l.reader]:
|
if node_level[l.driver] == node_level[l.reader]:
|
||||||
s.node(f'_{l.index}_')
|
dot.node(f'_{l.index}_')
|
||||||
dot.edge(driver, f'_{l.index}_', style='dotted', label=label)
|
dot.edge(driver, f'_{l.index}_', style='dotted', label=label)
|
||||||
dot.edge(f'_{l.index}_', reader, style='dotted', label=label)
|
dot.edge(f'_{l.index}_', reader, style='dotted', label=label)
|
||||||
elif node_level[l.driver] > node_level[l.reader]:
|
elif node_level[l.driver] > node_level[l.reader]:
|
||||||
|
|||||||
+19
-10
@@ -6,7 +6,7 @@ as an intermediate representation (:class:`DefFile` object).
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from lark import Lark, Transformer, Tree
|
from lark import Lark, Transformer
|
||||||
|
|
||||||
from kyupy import readtext
|
from kyupy import readtext
|
||||||
|
|
||||||
@@ -85,6 +85,8 @@ class DefFile:
|
|||||||
self.pins = {}
|
self.pins = {}
|
||||||
self.specialnets = {}
|
self.specialnets = {}
|
||||||
self.nets = {}
|
self.nets = {}
|
||||||
|
self.design = ""
|
||||||
|
self.diearea = 0
|
||||||
|
|
||||||
|
|
||||||
class DefTransformer(Transformer):
|
class DefTransformer(Transformer):
|
||||||
@@ -140,9 +142,11 @@ class DefTransformer(Transformer):
|
|||||||
def comp_stmt(self, args):
|
def comp_stmt(self, args):
|
||||||
name = args[0].value
|
name = args[0].value
|
||||||
kind = args[1].value
|
kind = args[1].value
|
||||||
point = args[2]
|
for aidx in range(2, len(args)):
|
||||||
orientation = args[3].value
|
if args[aidx] in ('PLACED', 'FIXED'):
|
||||||
self.def_file.components[name] = (kind, point, orientation)
|
point = args[aidx+1]
|
||||||
|
orientation = args[aidx+2].value
|
||||||
|
self.def_file.components[name] = (kind, point, orientation)
|
||||||
|
|
||||||
def pins_stmt(self, args):
|
def pins_stmt(self, args):
|
||||||
pin = DefPin(args[0].value)
|
pin = DefPin(args[0].value)
|
||||||
@@ -207,6 +211,7 @@ GRAMMAR = r"""
|
|||||||
| /DIEAREA/ point+ ";"
|
| /DIEAREA/ point+ ";"
|
||||||
| /ROW/ ID ID NUMBER NUMBER ID do_step ";"
|
| /ROW/ ID ID NUMBER NUMBER ID do_step ";"
|
||||||
| /TRACKS/ /[XY]/ NUMBER "DO" NUMBER "STEP" NUMBER "LAYER" ID ";"
|
| /TRACKS/ /[XY]/ NUMBER "DO" NUMBER "STEP" NUMBER "LAYER" ID ";"
|
||||||
|
| /GCELLGRID/ /[XY]/ NUMBER "DO" NUMBER "STEP" NUMBER ";"
|
||||||
| propdef | vias | nondef | comp | pins | pinprop | spnets | nets
|
| propdef | vias | nondef | comp | pins | pinprop | spnets | nets
|
||||||
|
|
||||||
propdef: "PROPERTYDEFINITIONS" propdef_stmt* "END" "PROPERTYDEFINITIONS"
|
propdef: "PROPERTYDEFINITIONS" propdef_stmt* "END" "PROPERTYDEFINITIONS"
|
||||||
@@ -228,7 +233,9 @@ GRAMMAR = r"""
|
|||||||
| "+" /VIA/ ID )* ";"
|
| "+" /VIA/ ID )* ";"
|
||||||
|
|
||||||
comp: "COMPONENTS" NUMBER ";" comp_stmt* "END" "COMPONENTS"
|
comp: "COMPONENTS" NUMBER ";" comp_stmt* "END" "COMPONENTS"
|
||||||
comp_stmt: "-" ID ID "+" "PLACED" point ID ";"
|
comp_stmt: "-" ID ID ( "+" /PLACED/ point ID
|
||||||
|
| "+" /FIXED/ point ID
|
||||||
|
| "+" /SOURCE/ ID )* ";"
|
||||||
|
|
||||||
pins: "PINS" NUMBER ";" pins_stmt* "END" "PINS"
|
pins: "PINS" NUMBER ";" pins_stmt* "END" "PINS"
|
||||||
pins_stmt: "-" ID pins_opt* ";"
|
pins_stmt: "-" ID pins_opt* ";"
|
||||||
@@ -239,6 +246,8 @@ GRAMMAR = r"""
|
|||||||
| "+" /PORT/
|
| "+" /PORT/
|
||||||
| "+" /LAYER/ ID point point
|
| "+" /LAYER/ ID point point
|
||||||
| "+" /PLACED/ point ID
|
| "+" /PLACED/ point ID
|
||||||
|
| "+" /FIXED/ point ID
|
||||||
|
|
||||||
|
|
||||||
pinprop: "PINPROPERTIES" NUMBER ";" pinprop_stmt* "END" "PINPROPERTIES"
|
pinprop: "PINPROPERTIES" NUMBER ";" pinprop_stmt* "END" "PINPROPERTIES"
|
||||||
pinprop_stmt: "-" "PIN" ID "+" "PROPERTY" ID STRING ";"
|
pinprop_stmt: "-" "PIN" ID "+" "PROPERTY" ID STRING ";"
|
||||||
@@ -266,10 +275,10 @@ GRAMMAR = r"""
|
|||||||
wire: ID wire_opt points
|
wire: ID wire_opt points
|
||||||
wire_opt: ( "TAPER" | "TAPERRULE" ID )? ("STYLE" ID)?
|
wire_opt: ( "TAPER" | "TAPERRULE" ID )? ("STYLE" ID)?
|
||||||
|
|
||||||
points: point ( point | points_via )+
|
points: point ( point | points_via_or_rect )+
|
||||||
points_via: ID ORIENTATION?
|
points_via_or_rect: ID ( ORIENTATION? | "(" (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) ")" )
|
||||||
|
|
||||||
point: "(" (NUMBER|/\*/) (NUMBER|/\*/) NUMBER? ")"
|
point: "(" (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) NUMBER? ")"
|
||||||
|
|
||||||
do_step: "DO" NUMBER "BY" NUMBER "STEP" (NUMBER|SIGNED_NUMBER) (NUMBER|SIGNED_NUMBER)
|
do_step: "DO" NUMBER "BY" NUMBER "STEP" (NUMBER|SIGNED_NUMBER) (NUMBER|SIGNED_NUMBER)
|
||||||
|
|
||||||
@@ -284,9 +293,9 @@ GRAMMAR = r"""
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def parse(text):
|
def parse(text) -> DefFile:
|
||||||
"""Parses the given ``text`` and returns a :class:`DefFile` object."""
|
"""Parses the given ``text`` and returns a :class:`DefFile` object."""
|
||||||
return Lark(GRAMMAR, parser="lalr", transformer=DefTransformer()).parse(text)
|
return Lark(GRAMMAR, parser="lalr", transformer=DefTransformer()).parse(text) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def load(file):
|
def load(file):
|
||||||
|
|||||||
+6
-5
@@ -49,7 +49,7 @@ from collections.abc import Iterable
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from numpy.typing import DTypeLike
|
from numpy.typing import DTypeLike
|
||||||
|
|
||||||
from . import numba, hr_bytes
|
from . import numba, hr_bytes, NestedNumericList
|
||||||
|
|
||||||
|
|
||||||
ZERO = 0b000
|
ZERO = 0b000
|
||||||
@@ -104,7 +104,7 @@ _mv_merge = np.array([[0,0,0,1,1,1,1,1], # 0
|
|||||||
], dtype=np.uint8)
|
], dtype=np.uint8)
|
||||||
|
|
||||||
|
|
||||||
def interpret(value):
|
def interpret(value) -> int|NestedNumericList:
|
||||||
"""Converts characters, strings, and lists of them to lists of logic constants defined above.
|
"""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.
|
:param value: A character (string of length 1), Boolean, Integer, None, or Iterable.
|
||||||
@@ -123,11 +123,12 @@ def interpret(value):
|
|||||||
return UNKNOWN
|
return UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def mvarray(*a):
|
def mvarray(*a) -> np.ndarray[tuple[int]|tuple[int,int],np.dtype[np.uint8]]:
|
||||||
"""Converts (lists of) Boolean values or strings into a multi-valued array.
|
"""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.
|
Iterable over characters, Booleans, Integers (0,1), None are interpreted as a single pattern.
|
||||||
Use this function to convert strings into multi-valued arrays.
|
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.
|
||||||
"""
|
"""
|
||||||
mva = np.array(interpret(a), dtype=np.uint8)
|
mva = np.array(interpret(a), dtype=np.uint8)
|
||||||
if mva.ndim < 2: return mva
|
if mva.ndim < 2: return mva
|
||||||
|
|||||||
+48
-12
@@ -1,12 +1,24 @@
|
|||||||
"""A high-throughput combinational logic simulator.
|
"""High-throughput combinational logic simulators.
|
||||||
|
|
||||||
The class :py:class:`~kyupy.logic_sim.LogicSim` performs parallel simulations of the combinational part of a circuit.
|
These simulators take batches of input assignments to a logic circuit and compute batches of output values.
|
||||||
The logic operations are performed bit-parallel on packed numpy arrays (see bit-parallel (bp) array description in :py:mod:`~kyupy.logic`).
|
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.
|
||||||
Simple sequential circuits can be simulated by repeated assignments and propagations.
|
All simulations within a batch are mutually independent.
|
||||||
However, this simulator ignores the clock network and simply assumes that all state-elements are clocked all the time.
|
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
import warnings
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@@ -17,6 +29,10 @@ from .circuit import Circuit, Line
|
|||||||
class LogicSim(sim.SimOps):
|
class LogicSim(sim.SimOps):
|
||||||
"""A bit-parallel naïve combinational simulator for 2-, 4-, or 8-valued logic.
|
"""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 circuit: The circuit to simulate.
|
||||||
:param sims: The number of parallel logic simulations to perform.
|
:param sims: The number of parallel logic simulations to perform.
|
||||||
:param m: The arity of the logic, must be 2, 4, or 8.
|
:param m: The arity of the logic, must be 2, 4, or 8.
|
||||||
@@ -24,6 +40,9 @@ class LogicSim(sim.SimOps):
|
|||||||
:param strip_forks: If True, forks are not included in the simulation model to save memory and simulation time.
|
: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):
|
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]
|
assert m in [2, 4, 8]
|
||||||
super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks)
|
super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks)
|
||||||
self.m = m
|
self.m = m
|
||||||
@@ -32,6 +51,7 @@ class LogicSim(sim.SimOps):
|
|||||||
nbytes = cdiv(sims, 8)
|
nbytes = cdiv(sims, 8)
|
||||||
|
|
||||||
self.c = np.zeros((self.c_len, self.mdim, nbytes), dtype=np.uint8)
|
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)
|
self.s = np.zeros((2, self.s_len, 3, nbytes), dtype=np.uint8)
|
||||||
"""Logic values of the sequential elements (flip-flops) and ports.
|
"""Logic values of the sequential elements (flip-flops) and ports.
|
||||||
|
|
||||||
@@ -78,7 +98,7 @@ class LogicSim(sim.SimOps):
|
|||||||
t1 = self.c_locs[self.tmp2_idx]
|
t1 = self.c_locs[self.tmp2_idx]
|
||||||
if self.m == 2:
|
if self.m == 2:
|
||||||
if inject_cb is None:
|
if inject_cb is None:
|
||||||
c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
|
c_prop_2v_cpu(self.ops, self.c_locs, self.c, self.c_dirty, int(fault_line), fault_mask, int(fault_model))
|
||||||
else:
|
else:
|
||||||
for op, o0l, i0l, i1l, i2l, i3l in self.ops[:,:6]:
|
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)]
|
o0, i0, i1, i2, i3 = [self.c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
|
||||||
@@ -337,6 +357,19 @@ class LogicSim2V(sim.SimOps):
|
|||||||
Storage locations are indirectly addressed.
|
Storage locations are indirectly addressed.
|
||||||
Data for line `l` is in `self.c[self.c_locs[l]]`.
|
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)
|
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.
|
"""Logic values assigned to the ports and flip-flops of the circuit.
|
||||||
|
|
||||||
@@ -371,7 +404,7 @@ class LogicSim2V(sim.SimOps):
|
|||||||
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
|
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
|
||||||
fault_mask2[:len(fault_mask)] = fault_mask
|
fault_mask2[:len(fault_mask)] = fault_mask
|
||||||
fault_mask = fault_mask2
|
fault_mask = fault_mask2
|
||||||
c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
|
c_prop_2v_cpu(self.ops, self.c_locs, self.c, self.c_dirty, int(fault_line), fault_mask, int(fault_model))
|
||||||
|
|
||||||
def c_to_s(self):
|
def c_to_s(self):
|
||||||
"""Captures the results of the combinational portion into ``self.s_result``.
|
"""Captures the results of the combinational portion into ``self.s_result``.
|
||||||
@@ -405,9 +438,11 @@ class LogicSim2V(sim.SimOps):
|
|||||||
|
|
||||||
|
|
||||||
@numba.njit
|
@numba.njit
|
||||||
def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
|
def c_prop_2v_cpu(ops, c_locs, c, c_dirty, fault_line, fault_mask, fault_model):
|
||||||
for op, o0l, i0l, i1l, i2l, i3l in ops[:,:6]:
|
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)]
|
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]
|
if op == sim.BUF1: c[o0]=c[i0]
|
||||||
elif op == sim.INV1: c[o0] = ~c[i0]
|
elif op == sim.INV1: c[o0] = ~c[i0]
|
||||||
elif op == sim.AND2: c[o0] = c[i0] & c[i1]
|
elif op == sim.AND2: c[o0] = c[i0] & c[i1]
|
||||||
@@ -447,8 +482,9 @@ def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
|
|||||||
c[o0] = c[o0] & ~fault_mask
|
c[o0] = c[o0] & ~fault_mask
|
||||||
elif fault_model == 1:
|
elif fault_model == 1:
|
||||||
c[o0] = c[o0] | fault_mask
|
c[o0] = c[o0] | fault_mask
|
||||||
else:
|
elif fault_model == 2:
|
||||||
c[o0] = c[o0] ^ fault_mask
|
c[o0] = c[o0] ^ fault_mask
|
||||||
|
c_dirty[o0] = 1
|
||||||
|
|
||||||
|
|
||||||
class LogicSim4V(sim.SimOps):
|
class LogicSim4V(sim.SimOps):
|
||||||
@@ -473,7 +509,7 @@ class LogicSim4V(sim.SimOps):
|
|||||||
Storage locations are indirectly addressed.
|
Storage locations are indirectly addressed.
|
||||||
Data for line `l` is in `self.c[self.c_locs[l]]`.
|
Data for line `l` is in `self.c[self.c_locs[l]]`.
|
||||||
"""
|
"""
|
||||||
self.s_assign = np.zeros((self.s_len, self.sims), dtype=np.uint8)
|
self.s_assign = np.full((self.s_len, self.sims), logic.UNASSIGNED, dtype=np.uint8)
|
||||||
"""Logic values assigned to the ports and flip-flops of the circuit.
|
"""Logic values assigned to the ports and flip-flops of the circuit.
|
||||||
The simulator reads (P)PI values from here.
|
The simulator reads (P)PI values from here.
|
||||||
Values assigned to PO positions are ignored.
|
Values assigned to PO positions are ignored.
|
||||||
@@ -481,7 +517,7 @@ class LogicSim4V(sim.SimOps):
|
|||||||
First index is the port position (defined by `self.circuit.s_nodes`), second
|
First index is the port position (defined by `self.circuit.s_nodes`), second
|
||||||
index is the pattern index (0 ... `self.sims-1`).
|
index is the pattern index (0 ... `self.sims-1`).
|
||||||
"""
|
"""
|
||||||
self.s_result = np.zeros((self.s_len, self.sims), dtype=np.uint8)
|
self.s_result = np.full((self.s_len, self.sims), logic.UNASSIGNED, dtype=np.uint8)
|
||||||
"""Logic values at the ports and flip-flops of the circuit after simulation.
|
"""Logic values at the ports and flip-flops of the circuit after simulation.
|
||||||
The simulator writes (P)PO values here.
|
The simulator writes (P)PO values here.
|
||||||
Values assigned to PI positions are ignored.
|
Values assigned to PI positions are ignored.
|
||||||
@@ -739,7 +775,7 @@ class LogicSim6V(sim.SimOps):
|
|||||||
nbytes = cdiv(sims, 8)
|
nbytes = cdiv(sims, 8)
|
||||||
|
|
||||||
self.c = np.zeros((self.c_len, 3, nbytes), dtype=np.uint8)
|
self.c = np.zeros((self.c_len, 3, nbytes), dtype=np.uint8)
|
||||||
self.s = np.zeros((2, self.s_len, self.sims), dtype=np.uint8)
|
self.s = np.full((2, self.s_len, self.sims), logic.UNASSIGNED, dtype=np.uint8)
|
||||||
"""Logic values of the sequential elements (flip-flops) and ports.
|
"""Logic values of the sequential elements (flip-flops) and ports.
|
||||||
|
|
||||||
It is a pair of arrays in mv storage format:
|
It is a pair of arrays in mv storage format:
|
||||||
|
|||||||
+31
-14
@@ -6,6 +6,7 @@ import numpy as np
|
|||||||
|
|
||||||
from . import log
|
from . import log
|
||||||
from .circuit import Circuit
|
from .circuit import Circuit
|
||||||
|
from .techlib import KYUPY
|
||||||
|
|
||||||
BUF1 = np.uint16(0b1010_1010_1010_1010)
|
BUF1 = np.uint16(0b1010_1010_1010_1010)
|
||||||
INV1 = ~BUF1
|
INV1 = ~BUF1
|
||||||
@@ -159,7 +160,7 @@ class SimOps:
|
|||||||
"""A static scheduler that translates a Circuit into a topologically sorted list of basic logic operations (self.ops) and
|
"""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.
|
a memory mapping (self.c_locs, self.c_caps) for use in simulators.
|
||||||
|
|
||||||
:param circuit: The circuit to create a schedule for.
|
:param circuit: The circuit to create a schedule for. Must contain cells from KYUPY techlib only.
|
||||||
:param strip_forks: If enabled, the scheduler will not include fork nodes to safe simulation time.
|
: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.
|
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
|
:param c_reuse: If enabled, memory of intermediate signal waveforms will be re-used. This greatly reduces
|
||||||
@@ -167,7 +168,7 @@ class SimOps:
|
|||||||
"""
|
"""
|
||||||
def __init__(self, circuit: Circuit, c_caps=1, c_caps_min=1, a_ctrl=None, c_reuse=False, strip_forks=False):
|
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.circuit = circuit
|
||||||
self.s_len = len(circuit.s_nodes)
|
self.s_len = len(circuit.s_nodes(KYUPY))
|
||||||
|
|
||||||
if isinstance(c_caps, int):
|
if isinstance(c_caps, int):
|
||||||
c_caps = [c_caps] * (len(circuit.lines)+3)
|
c_caps = [c_caps] * (len(circuit.lines)+3)
|
||||||
@@ -187,11 +188,11 @@ class SimOps:
|
|||||||
# ALAP-toposort the circuit into self.ops
|
# ALAP-toposort the circuit into self.ops
|
||||||
levels = []
|
levels = []
|
||||||
|
|
||||||
ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes))
|
ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes(KYUPY)))
|
||||||
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
|
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
|
||||||
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
|
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 root_nodes if len(n.ins) > 0 ]
|
level_lines = [n.ins[0] for n in sorted(root_nodes, key=lambda n: n.index) if len(n.ins) > 0 ]
|
||||||
# FIXME: Should probably instanciate buffers for PPOs and attach DFF clocks
|
# 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
|
while len(level_lines) > 0: # traverse the circuit level-wise back towards (P)PIs
|
||||||
@@ -200,11 +201,15 @@ class SimOps:
|
|||||||
|
|
||||||
for l in level_lines:
|
for l in level_lines:
|
||||||
n = l.driver
|
n = l.driver
|
||||||
if len(n.ins) > 4:
|
assert len(n.ins) <= 4, f'Too many input pins for node {n}. Map circuit to KYUPY techlib first using resolve_tlib_cells().'
|
||||||
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]]
|
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:
|
if n in ppio2idx:
|
||||||
in_idxs[0] = self.ppi_offset + ppio2idx[n]
|
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])
|
||||||
if l.driver_pin == 1 and 'dff' in n.kind.lower(): # second output of DFF is inverted
|
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]))
|
level_ops.append((INV1, l.index, *in_idxs, *a_ctrl[l]))
|
||||||
else:
|
else:
|
||||||
@@ -225,10 +230,8 @@ class SimOps:
|
|||||||
if in_idxs[2] == self.zero_idx:
|
if in_idxs[2] == self.zero_idx:
|
||||||
sp = prims[2]
|
sp = prims[2]
|
||||||
break
|
break
|
||||||
if sp is None:
|
assert sp is not None, f'Unsupported node type {n}. Map circuit to KYUPY techlib first using resolce_tlib_cells().'
|
||||||
log.warn(f'ignored cell of unknown type: {n}')
|
level_ops.append((sp, l.index, *in_idxs, *a_ctrl[l]))
|
||||||
else:
|
|
||||||
level_ops.append((sp, l.index, *in_idxs, *a_ctrl[l]))
|
|
||||||
|
|
||||||
if len(level_ops) > 0: levels.append(level_ops)
|
if len(level_ops) > 0: levels.append(level_ops)
|
||||||
level_lines = prev_level_lines
|
level_lines = prev_level_lines
|
||||||
@@ -271,7 +274,7 @@ class SimOps:
|
|||||||
ref_count[self.tmp2_idx] += 1
|
ref_count[self.tmp2_idx] += 1
|
||||||
|
|
||||||
# allocate and keep memory for PI/PPI, keep memory for PO/PPO (allocated later)
|
# allocate and keep memory for PI/PPI, keep memory for PO/PPO (allocated later)
|
||||||
for i, n in enumerate(circuit.s_nodes):
|
for i, n in enumerate(circuit.s_nodes(KYUPY)):
|
||||||
if 'dff' in n.kind.lower() or len(n.ins) == 0: # PPI or PI
|
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
|
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
|
ref_count[self.ppi_offset + i] += 1
|
||||||
@@ -310,10 +313,15 @@ class SimOps:
|
|||||||
self.c_locs[lidx], self.c_caps[lidx] = self.c_locs[stem], self.c_caps[stem]
|
self.c_locs[lidx], self.c_caps[lidx] = self.c_locs[stem], self.c_caps[stem]
|
||||||
|
|
||||||
# copy memory location to PO/PPO area
|
# copy memory location to PO/PPO area
|
||||||
for i, n in enumerate(circuit.s_nodes):
|
for i, n in enumerate(circuit.s_nodes(KYUPY)):
|
||||||
if len(n.ins) > 0:
|
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]]
|
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
|
# line use information
|
||||||
self.line_use_start = np.full(self.c_locs_len, -1, dtype=np.int32)
|
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)
|
self.line_use_stop = np.full(self.c_locs_len, len(self.levels), dtype=np.int32)
|
||||||
@@ -348,3 +356,12 @@ class SimOps:
|
|||||||
|
|
||||||
self.pippi_c_locs = np.concatenate([self.pi_c_locs, self.ppi_c_locs])
|
self.pippi_c_locs = np.concatenate([self.pi_c_locs, self.ppi_c_locs])
|
||||||
self.poppo_c_locs = np.concatenate([self.po_c_locs, self.ppo_c_locs])
|
self.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)
|
||||||
+7
-5
@@ -106,7 +106,7 @@ class StilFile:
|
|||||||
tests[pi_map, i] = logic.mvarray(p.capture['_pi'][0])
|
tests[pi_map, i] = logic.mvarray(p.capture['_pi'][0])
|
||||||
return tests
|
return tests
|
||||||
|
|
||||||
def tests_loc(self, circuit, init_filter=None, launch_filter=None):
|
def tests_loc(self, circuit, tlib, init_filter=None, launch_filter=None):
|
||||||
"""Assembles and returns a LoC scan test pattern set for given circuit.
|
"""Assembles and returns a LoC scan test pattern set for given circuit.
|
||||||
|
|
||||||
This function assumes a launch-on-capture (LoC) delay test.
|
This function assumes a launch-on-capture (LoC) delay test.
|
||||||
@@ -139,7 +139,9 @@ class StilFile:
|
|||||||
init[scan_maps[si_port], i] = pattern
|
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])
|
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)
|
if init_filter: init = init_filter(init)
|
||||||
sim8v = LogicSim(circuit, init.shape[-1], m=8)
|
circuit_resolved = circuit.copy()
|
||||||
|
circuit_resolved.resolve_tlib_cells(tlib)
|
||||||
|
sim8v = LogicSim(circuit_resolved, init.shape[-1], m=8)
|
||||||
sim8v.s[0] = logic.mv_to_bp(init)
|
sim8v.s[0] = logic.mv_to_bp(init)
|
||||||
sim8v.s_to_c()
|
sim8v.s_to_c()
|
||||||
sim8v.c_prop()
|
sim8v.c_prop()
|
||||||
@@ -263,12 +265,12 @@ GRAMMAR = r"""
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def parse(text):
|
def parse(text) -> StilFile:
|
||||||
"""Parses the given ``text`` and returns a :class:`StilFile` object."""
|
"""Parses the given ``text`` and returns a :class:`StilFile` object."""
|
||||||
return Lark(GRAMMAR, parser="lalr", transformer=StilTransformer()).parse(text)
|
return Lark(GRAMMAR, parser="lalr", transformer=StilTransformer()).parse(text) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def load(file):
|
def load(file) -> StilFile:
|
||||||
"""Parses the contents of ``file`` and returns a :class:`StilFile` object.
|
"""Parses the contents of ``file`` and returns a :class:`StilFile` object.
|
||||||
|
|
||||||
Files with `.gz`-suffix are decompressed on-the-fly.
|
Files with `.gz`-suffix are decompressed on-the-fly.
|
||||||
|
|||||||
+26
-3
@@ -38,9 +38,11 @@ class TechLib:
|
|||||||
else:
|
else:
|
||||||
pin_dict[n.name] = (o_idx, True)
|
pin_dict[n.name] = (o_idx, True)
|
||||||
o_idx += 1
|
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]
|
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)]:
|
for name in [''.join(item) for item in product(*parts)]:
|
||||||
self.cells[name] = (c, pin_dict)
|
self.cells[name] = (c, pin_dict, has_dff, has_latch)
|
||||||
|
|
||||||
def pin_index(self, kind, pin):
|
def pin_index(self, kind, pin):
|
||||||
"""Returns a pin list position for a given node kind and pin name."""
|
"""Returns a pin list position for a given node kind and pin name."""
|
||||||
@@ -48,13 +50,13 @@ class TechLib:
|
|||||||
assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}'
|
assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}'
|
||||||
return self.cells[kind][1][pin][0]
|
return self.cells[kind][1][pin][0]
|
||||||
|
|
||||||
def pin_name(self, kind, pos, output=False):
|
def pin_name(self, kind: str, pos: int, output=False) -> str:
|
||||||
"""Returns the pin name for a given node kind, list position, and direction."""
|
"""Returns the pin name for a given node kind, list position, and direction."""
|
||||||
assert kind in self.cells, f'Unknown cell: {kind}'
|
assert kind in self.cells, f'Unknown cell: {kind}'
|
||||||
for name, (ppos, isout) in self.cells[kind][1].items():
|
for name, (ppos, isout) in self.cells[kind][1].items():
|
||||||
if isout == output and ppos == pos:
|
if isout == output and ppos == pos:
|
||||||
return name
|
return name
|
||||||
return None
|
raise ValueError(f'Invalid {"output" if output else "input"} pin position {pos} for cell: {kind}')
|
||||||
|
|
||||||
def pin_is_output(self, kind, pin):
|
def pin_is_output(self, kind, pin):
|
||||||
"""Returns True, if given pin name of a node kind is an output."""
|
"""Returns True, if given pin name of a node kind is an output."""
|
||||||
@@ -62,6 +64,26 @@ class TechLib:
|
|||||||
assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}'
|
assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}'
|
||||||
return self.cells[kind][1][pin][1]
|
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"""
|
KYUPY = TechLib(r"""
|
||||||
BUF1 input(i0) output(o) o=BUF1(i0) ;
|
BUF1 input(i0) output(o) o=BUF1(i0) ;
|
||||||
@@ -98,6 +120,7 @@ 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) ;
|
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) ;
|
MUX21 input(i0,i1,i2) output(o) o=MUX21(i0,i1,i2) ;
|
||||||
DFF input(D,CLK) output(Q) Q=DFF(D,CLK) ;
|
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.
|
"""A synthetic library of all KyuPy simulation primitives.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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
|
||||||
@@ -2,7 +2,7 @@ import pickle
|
|||||||
|
|
||||||
from kyupy.circuit import GrowingList, Circuit, Node, Line
|
from kyupy.circuit import GrowingList, Circuit, Node, Line
|
||||||
from kyupy import verilog, bench
|
from kyupy import verilog, bench
|
||||||
from kyupy.techlib import SAED32
|
from kyupy.techlib import KYUPY, SAED32
|
||||||
|
|
||||||
def test_growing_list():
|
def test_growing_list():
|
||||||
gl = GrowingList()
|
gl = GrowingList()
|
||||||
@@ -155,7 +155,7 @@ def test_substitute():
|
|||||||
|
|
||||||
def test_resolve(mydir):
|
def test_resolve(mydir):
|
||||||
c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32)
|
c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32)
|
||||||
s_names = [n.name for n in c.s_nodes]
|
s_names = [n.name for n in c.s_nodes(SAED32)]
|
||||||
c.resolve_tlib_cells(SAED32)
|
c.resolve_tlib_cells(SAED32)
|
||||||
s_names_prim = [n.name for n in c.s_nodes]
|
s_names_prim = [n.name for n in c.s_nodes(KYUPY)]
|
||||||
assert s_names == s_names_prim, 'resolve_tlib_cells does not preserve names or order of s_nodes'
|
assert s_names == s_names_prim, 'resolve_tlib_cells does not preserve names or order of s_nodes'
|
||||||
+49
-54
@@ -2,8 +2,8 @@ import numpy as np
|
|||||||
|
|
||||||
from kyupy.logic_sim import LogicSim, LogicSim2V, LogicSim4V, LogicSim6V
|
from kyupy.logic_sim import LogicSim, LogicSim2V, LogicSim4V, LogicSim6V
|
||||||
from kyupy import bench, logic, sim, verilog
|
from kyupy import bench, logic, sim, verilog
|
||||||
from kyupy.logic import mvarray, bparray, bp_to_mv, mv_to_bp
|
from kyupy.logic import mv_str, mvarray, bparray, bp_to_mv, mv_to_bp
|
||||||
from kyupy.techlib import SAED90
|
from kyupy.techlib import SAED90, KYUPY
|
||||||
|
|
||||||
def test_dangling():
|
def test_dangling():
|
||||||
c = verilog.parse('''
|
c = verilog.parse('''
|
||||||
@@ -20,6 +20,18 @@ def test_dangling():
|
|||||||
sim.simulate(p)
|
sim.simulate(p)
|
||||||
assert p[1,0] == logic.ONE
|
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):
|
def assert_equal_shape_and_contents(actual, desired):
|
||||||
desired = np.array(desired, dtype=np.uint8)
|
desired = np.array(desired, dtype=np.uint8)
|
||||||
assert actual.shape == desired.shape
|
assert actual.shape == desired.shape
|
||||||
@@ -105,7 +117,7 @@ def test_LogicSim6V_simprims(mydir):
|
|||||||
sim1.simulate(tests1)
|
sim1.simulate(tests1)
|
||||||
sim2.simulate(tests2)
|
sim2.simulate(tests2)
|
||||||
for loc1, loc2 in zip(c1.io_locs('o'), c2.io_locs('o')):
|
for loc1, loc2 in zip(c1.io_locs('o'), c2.io_locs('o')):
|
||||||
n = c1.s_nodes[loc1]
|
n = c1.s_nodes(KYUPY)[loc1]
|
||||||
if (tests1[loc1] != tests2[loc2]).any():
|
if (tests1[loc1] != tests2[loc2]).any():
|
||||||
print(f'Mismatch at output {n}')
|
print(f'Mismatch at output {n}')
|
||||||
np.testing.assert_array_equal(
|
np.testing.assert_array_equal(
|
||||||
@@ -150,14 +162,12 @@ def test_2v():
|
|||||||
o31=OAI211(i0,i1,i2,i3)
|
o31=OAI211(i0,i1,i2,i3)
|
||||||
o32=MUX21(i0,i1,i2)
|
o32=MUX21(i0,i1,i2)
|
||||||
''')
|
''')
|
||||||
s = LogicSim(c, 16, m=2)
|
s = LogicSim2V(c, 16)
|
||||||
bpa = logic.bparray([f'{i:04b}'+('-'*(s.s_len-4)) for i in range(16)])
|
s.s_assign[...] = logic.mvarray([f'{i:04b}'+('-'*(s.s_len-4)) for i in range(16)])
|
||||||
s.s[0] = bpa
|
|
||||||
s.s_to_c()
|
s.s_to_c()
|
||||||
s.c_prop()
|
s.c_prop()
|
||||||
s.c_to_s()
|
s.c_to_s()
|
||||||
mva = logic.bp_to_mv(s.s[1])
|
for res, exp in zip(logic.packbits(s.s_result[4:], dtype=np.uint32), [
|
||||||
for res, exp in zip(logic.packbits(mva[4:], dtype=np.uint32), [
|
|
||||||
sim.BUF1, sim.INV1,
|
sim.BUF1, sim.INV1,
|
||||||
sim.AND2, sim.AND3, sim.AND4,
|
sim.AND2, sim.AND3, sim.AND4,
|
||||||
sim.NAND2, sim.NAND3, sim.NAND4,
|
sim.NAND2, sim.NAND3, sim.NAND4,
|
||||||
@@ -178,19 +188,17 @@ def test_2v():
|
|||||||
|
|
||||||
def test_4v():
|
def test_4v():
|
||||||
c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')
|
c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')
|
||||||
s = LogicSim(c, 16, m=4)
|
s = LogicSim4V(c, 16)
|
||||||
assert s.s_len == 5
|
assert s.s_len == 5
|
||||||
bpa = bparray(
|
s.s_assign[...] = mvarray(
|
||||||
'00---', '01---', '0----', '0X---',
|
'00---', '01---', '0----', '0X---',
|
||||||
'10---', '11---', '1----', '1X---',
|
'10---', '11---', '1----', '1X---',
|
||||||
'-0---', '-1---', '-----', '-X---',
|
'-0---', '-1---', '-----', '-X---',
|
||||||
'X0---', 'X1---', 'X----', 'XX---')
|
'X0---', 'X1---', 'X----', 'XX---')
|
||||||
s.s[0] = bpa
|
|
||||||
s.s_to_c()
|
s.s_to_c()
|
||||||
s.c_prop()
|
s.c_prop()
|
||||||
s.c_to_s()
|
s.c_to_s()
|
||||||
mva = bp_to_mv(s.s[1])
|
assert_equal_shape_and_contents(s.s_result, mvarray(
|
||||||
assert_equal_shape_and_contents(mva, mvarray(
|
|
||||||
'--001', '--011', '--0X1', '--0X1',
|
'--001', '--011', '--0X1', '--0X1',
|
||||||
'--010', '--110', '--X10', '--X10',
|
'--010', '--110', '--X10', '--X10',
|
||||||
'--0XX', '--X1X', '--XXX', '--XXX',
|
'--0XX', '--X1X', '--XXX', '--XXX',
|
||||||
@@ -198,38 +206,34 @@ def test_4v():
|
|||||||
|
|
||||||
def test_4v_fault():
|
def test_4v_fault():
|
||||||
c = bench.parse('input(x, y) output(a) a=and(x,y)')
|
c = bench.parse('input(x, y) output(a) a=and(x,y)')
|
||||||
s = LogicSim(c, 16, m=4)
|
s = LogicSim4V(c, 16)
|
||||||
assert s.s_len == 3
|
assert s.s_len == 3
|
||||||
bpa = bparray(
|
s.s_assign[...] = mvarray(
|
||||||
'00-', '01-', '0--', '0X-',
|
'00-', '01-', '0--', '0X-',
|
||||||
'10-', '11-', '1--', '1X-',
|
'10-', '11-', '1--', '1X-',
|
||||||
'-0-', '-1-', '---', '-X-',
|
'-0-', '-1-', '---', '-X-',
|
||||||
'X0-', 'X1-', 'X--', 'XX-')
|
'X0-', 'X1-', 'X--', 'XX-')
|
||||||
s.s[0] = bpa
|
|
||||||
s.s_to_c()
|
s.s_to_c()
|
||||||
s.c_prop()
|
s.c_prop()
|
||||||
s.c_to_s()
|
s.c_to_s()
|
||||||
mva = bp_to_mv(s.s[1])
|
assert_equal_shape_and_contents(s.s_result, mvarray(
|
||||||
assert_equal_shape_and_contents(mva, mvarray(
|
|
||||||
'--0', '--0', '--0', '--0',
|
'--0', '--0', '--0', '--0',
|
||||||
'--0', '--1', '--X', '--X',
|
'--0', '--1', '--X', '--X',
|
||||||
'--0', '--X', '--X', '--X',
|
'--0', '--X', '--X', '--X',
|
||||||
'--0', '--X', '--X', '--X'))
|
'--0', '--X', '--X', '--X'))
|
||||||
fault_line = s.circuit.cells['a'].ins[0]
|
fault_line = s.circuit.cells['a'].ins[0]
|
||||||
s.s_to_c()
|
s.s_to_c()
|
||||||
s.c_prop(fault_line=fault_line, fault_model=1)
|
s.c_prop(fault_line=fault_line.index, fault_model=1)
|
||||||
s.c_to_s()
|
s.c_to_s()
|
||||||
mva = bp_to_mv(s.s[1])
|
assert_equal_shape_and_contents(s.s_result, mvarray(
|
||||||
assert_equal_shape_and_contents(mva, mvarray(
|
|
||||||
'--0', '--1', '--X', '--X',
|
'--0', '--1', '--X', '--X',
|
||||||
'--0', '--1', '--X', '--X',
|
'--0', '--1', '--X', '--X',
|
||||||
'--0', '--1', '--X', '--X',
|
'--0', '--1', '--X', '--X',
|
||||||
'--0', '--1', '--X', '--X'))
|
'--0', '--1', '--X', '--X'))
|
||||||
s.s_to_c()
|
s.s_to_c()
|
||||||
s.c_prop(fault_line=fault_line, fault_model=0)
|
s.c_prop(fault_line=fault_line.index, fault_model=0)
|
||||||
s.c_to_s()
|
s.c_to_s()
|
||||||
mva = bp_to_mv(s.s[1])
|
assert_equal_shape_and_contents(s.s_result, mvarray(
|
||||||
assert_equal_shape_and_contents(mva, mvarray(
|
|
||||||
'--0', '--0', '--0', '--0',
|
'--0', '--0', '--0', '--0',
|
||||||
'--0', '--0', '--0', '--0',
|
'--0', '--0', '--0', '--0',
|
||||||
'--0', '--0', '--0', '--0',
|
'--0', '--0', '--0', '--0',
|
||||||
@@ -255,11 +259,11 @@ def test_6v():
|
|||||||
resp = s.s[1].copy()
|
resp = s.s[1].copy()
|
||||||
|
|
||||||
exp_resp = np.copy(mva)
|
exp_resp = np.copy(mva)
|
||||||
exp_resp[:2] = logic.ZERO
|
exp_resp[:2] = logic.UNASSIGNED
|
||||||
np.testing.assert_allclose(resp, exp_resp)
|
np.testing.assert_allclose(resp, exp_resp)
|
||||||
|
|
||||||
|
|
||||||
def test_8v():
|
def xtest_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)')
|
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)
|
s = LogicSim(c, 64, m=8)
|
||||||
assert s.s_len == 6
|
assert s.s_len == 6
|
||||||
@@ -286,8 +290,8 @@ def test_8v():
|
|||||||
np.testing.assert_allclose(resp, exp_resp)
|
np.testing.assert_allclose(resp, exp_resp)
|
||||||
|
|
||||||
|
|
||||||
def test_loop():
|
def xtest_loop():
|
||||||
c = bench.parse('q=dff(d) d=not(q)')
|
c = bench.parse('q=DFF(d) d=NOT(q)')
|
||||||
s = LogicSim(c, 4, m=8)
|
s = LogicSim(c, 4, m=8)
|
||||||
assert s.s_len == 1
|
assert s.s_len == 1
|
||||||
mva = mvarray([['0'], ['1'], ['R'], ['F']])
|
mva = mvarray([['0'], ['1'], ['R'], ['F']])
|
||||||
@@ -313,8 +317,8 @@ def test_loop():
|
|||||||
# assert resp[3] == 'F'
|
# assert resp[3] == 'F'
|
||||||
|
|
||||||
|
|
||||||
def test_latch():
|
def xtest_latch():
|
||||||
c = bench.parse('input(d, t) output(q) q=latch(d, t)')
|
c = bench.parse('input(d, t) output(q) q=LATCH(d, t)')
|
||||||
s = LogicSim(c, 8, m=8)
|
s = LogicSim(c, 8, m=8)
|
||||||
assert s.s_len == 4
|
assert s.s_len == 4
|
||||||
mva = mvarray('00-0', '00-1', '01-0', '01-1', '10-0', '10-1', '11-0', '11-1')
|
mva = mvarray('00-0', '00-1', '01-0', '01-1', '10-0', '10-1', '11-0', '11-1')
|
||||||
@@ -327,19 +331,6 @@ def test_latch():
|
|||||||
# assert resp[i] == exp[i]
|
# 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):
|
def sim_and_compare(c, test_resp, m=8):
|
||||||
tests, resp = test_resp
|
tests, resp = test_resp
|
||||||
lsim = LogicSim(c, m=m, sims=tests.shape[1])
|
lsim = LogicSim(c, m=m, sims=tests.shape[1])
|
||||||
@@ -356,14 +347,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])}')
|
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
|
assert len(idxs) == 0
|
||||||
|
|
||||||
def sim_and_compare_6v(c, test_resp):
|
def sim_and_compare_cls(c, test_resp, SimCls):
|
||||||
tests, resp = test_resp
|
tests, resp = test_resp
|
||||||
lsim = LogicSim6V(c, sims=tests.shape[1])
|
lsim = SimCls(c, sims=tests.shape[1])
|
||||||
lsim.s[0] = tests
|
lsim.s_assign[...] = tests
|
||||||
lsim.s_to_c()
|
lsim.s_to_c()
|
||||||
lsim.c_prop()
|
lsim.c_prop()
|
||||||
lsim.c_to_s()
|
lsim.c_to_s()
|
||||||
resp_sim = lsim.s[1]
|
resp_sim = lsim.s_result
|
||||||
idxs, pats = np.nonzero(((resp == logic.ONE) & (resp_sim != logic.ONE)) | ((resp == logic.ZERO) & (resp_sim != logic.ZERO)))
|
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)):
|
for i, (idx, pat) in enumerate(zip(idxs, pats)):
|
||||||
if i >= 10:
|
if i >= 10:
|
||||||
@@ -374,28 +365,32 @@ def sim_and_compare_6v(c, test_resp):
|
|||||||
|
|
||||||
|
|
||||||
def test_b15_2ig_sa_2v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
|
def test_b15_2ig_sa_2v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
|
||||||
sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=2)
|
sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim2V)
|
||||||
|
|
||||||
|
|
||||||
def test_b15_2ig_sa_4v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
|
def test_b15_2ig_sa_4v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
|
||||||
sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=4)
|
sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim4V)
|
||||||
|
|
||||||
|
|
||||||
def test_b15_2ig_sa_6v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
|
def test_b15_2ig_sa_6v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
|
||||||
sim_and_compare_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)
|
||||||
|
|
||||||
|
|
||||||
def test_b15_2ig_sa_8v(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):
|
||||||
sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=8)
|
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):
|
def test_b15_4ig_sa_2v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
|
||||||
sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=2)
|
sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim2V)
|
||||||
|
|
||||||
|
|
||||||
def test_b15_4ig_sa_4v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
|
def test_b15_4ig_sa_4v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
|
||||||
sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=4)
|
sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim4V)
|
||||||
|
|
||||||
|
|
||||||
def test_b15_4ig_sa_8v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def xtest_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)
|
sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=8)
|
||||||
+1
-1
@@ -14,7 +14,7 @@ def test_b15(mydir):
|
|||||||
assert len(resp) > 0
|
assert len(resp) > 0
|
||||||
|
|
||||||
s2 = stil.load(mydir / 'b15_2ig.tf_nf.stil.gz')
|
s2 = stil.load(mydir / 'b15_2ig.tf_nf.stil.gz')
|
||||||
tests = s2.tests_loc(b15)
|
tests = s2.tests_loc(b15, SAED32)
|
||||||
resp = s2.responses(b15)
|
resp = s2.responses(b15)
|
||||||
assert len(tests) > 0
|
assert len(tests) > 0
|
||||||
assert len(resp) > 0
|
assert len(resp) > 0
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ def test_b01(mydir):
|
|||||||
|
|
||||||
assert len(c.nodes) == 139
|
assert len(c.nodes) == 139
|
||||||
assert len(c.lines) == 203
|
assert len(c.lines) == 203
|
||||||
stats = c.stats
|
stats = c.stats(SAED90)
|
||||||
assert stats['input'] == 6
|
assert stats['input'] == 6
|
||||||
assert stats['output'] == 3
|
assert stats['output'] == 3
|
||||||
assert stats['__seq__'] == 5
|
assert stats['__seq__'] == 5
|
||||||
@@ -19,7 +19,7 @@ def test_b15(mydir):
|
|||||||
c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32)
|
c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32)
|
||||||
assert len(c.nodes) == 12067
|
assert len(c.nodes) == 12067
|
||||||
assert len(c.lines) == 20731
|
assert len(c.lines) == 20731
|
||||||
stats = c.stats
|
stats = c.stats(SAED32)
|
||||||
assert stats['input'] == 40
|
assert stats['input'] == 40
|
||||||
assert stats['output'] == 71
|
assert stats['output'] == 71
|
||||||
assert stats['__seq__'] == 417
|
assert stats['__seq__'] == 417
|
||||||
@@ -29,7 +29,7 @@ def test_gates(mydir):
|
|||||||
c = verilog.load(mydir / 'gates.v', tlib=NANGATE45)
|
c = verilog.load(mydir / 'gates.v', tlib=NANGATE45)
|
||||||
assert len(c.nodes) == 18
|
assert len(c.nodes) == 18
|
||||||
assert len(c.lines) == 21
|
assert len(c.lines) == 21
|
||||||
stats = c.stats
|
stats = c.stats(NANGATE45)
|
||||||
assert stats['input'] == 3
|
assert stats['input'] == 3
|
||||||
assert stats['output'] == 4
|
assert stats['output'] == 4
|
||||||
assert stats['__seq__'] == 0
|
assert stats['__seq__'] == 0
|
||||||
@@ -39,7 +39,7 @@ def test_halton2(mydir):
|
|||||||
c = verilog.load(mydir / 'rng_haltonBase2.synth_yosys.v', tlib=SAED90)
|
c = verilog.load(mydir / 'rng_haltonBase2.synth_yosys.v', tlib=SAED90)
|
||||||
assert len(c.nodes) == 146
|
assert len(c.nodes) == 146
|
||||||
assert len(c.lines) == 210
|
assert len(c.lines) == 210
|
||||||
stats = c.stats
|
stats = c.stats(SAED90)
|
||||||
assert stats['input'] == 2
|
assert stats['input'] == 2
|
||||||
assert stats['output'] == 12
|
assert stats['output'] == 12
|
||||||
assert stats['__seq__'] == 12
|
assert stats['__seq__'] == 12
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from kyupy.wave_sim import WaveSim, WaveSimCuda, wave_eval_cpu, TMIN, TMAX
|
from kyupy.wave_sim import WaveSim, WaveSimCuda, wave_eval_cpu, TMIN, TMAX
|
||||||
from kyupy.logic_sim import LogicSim
|
from kyupy.logic_sim import LogicSim6V
|
||||||
from kyupy import logic, bench, sim
|
from kyupy import logic, bench, sim
|
||||||
from kyupy.logic import mvarray
|
from kyupy.logic import mvarray
|
||||||
|
|
||||||
@@ -175,12 +175,12 @@ def compare_to_logic_sim(wsim: WaveSim):
|
|||||||
resp |= ((resp ^ (resp >> 1)) & 1) << 2 # transitions
|
resp |= ((resp ^ (resp >> 1)) & 1) << 2 # transitions
|
||||||
resp[wsim.pi_s_locs] = logic.UNASSIGNED
|
resp[wsim.pi_s_locs] = logic.UNASSIGNED
|
||||||
|
|
||||||
lsim = LogicSim(wsim.circuit, tests.shape[-1])
|
lsim = LogicSim6V(wsim.circuit, tests.shape[-1])
|
||||||
lsim.s[0] = logic.mv_to_bp(tests)
|
lsim.s_assign[...] = tests
|
||||||
lsim.s_to_c()
|
lsim.s_to_c()
|
||||||
lsim.c_prop()
|
lsim.c_prop()
|
||||||
lsim.c_to_s()
|
lsim.c_to_s()
|
||||||
exp = logic.bp_to_mv(lsim.s[1])[:,:tests.shape[-1]]
|
exp = lsim.s_result[:,:tests.shape[-1]]
|
||||||
|
|
||||||
resp[resp == logic.PPULSE] = logic.ZERO
|
resp[resp == logic.PPULSE] = logic.ZERO
|
||||||
resp[resp == logic.NPULSE] = logic.ONE
|
resp[resp == logic.NPULSE] = logic.ONE
|
||||||
|
|||||||
Reference in new issue
Block a user