2 changed files with 93 additions and 0 deletions
@ -0,0 +1,64 @@
@@ -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)) |
||||
@ -0,0 +1,29 @@
@@ -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 |
||||
Loading…
Reference in new issue