2 changed files with 93 additions and 0 deletions
@ -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 @@ |
|||||||
|
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