mirror of
https://github.com/ole00/afterburner.git
synced 2024-11-28 15:49:49 +00:00
907 lines
33 KiB
Python
907 lines
33 KiB
Python
# Vendored from glasgow.protocol.jtag_svf
|
|
|
|
# Ref: https://www.asset-intertech.com/eresources/svf-serial-vector-format-specification-jtag-boundary-scan
|
|
# Accession: G00022
|
|
# Ref: http://www.jtagtest.com/pdf/svf_specification.pdf
|
|
# Accession: G00023
|
|
|
|
import re
|
|
from abc import ABCMeta, abstractmethod
|
|
from bitarray import bitarray
|
|
|
|
|
|
__all__ = ["SVFParser", "SVFEventHandler"]
|
|
|
|
|
|
def _hex_to_bitarray(input_nibbles):
|
|
byte_len = (len(input_nibbles) + 1) // 2
|
|
input_bytes = bytes.fromhex(input_nibbles.rjust(byte_len * 2, "0"))
|
|
bits = bitarray(endian="little")
|
|
bits.frombytes(input_bytes)
|
|
bits.reverse()
|
|
bits.bytereverse()
|
|
return bits
|
|
|
|
|
|
_commands = (
|
|
"ENDDR", "ENDIR", "FREQUENCY", "HDR", "HIR", "PIO", "PIOMAP", "RUNTEST",
|
|
"SDR", "SIR", "STATE", "TDR", "TIR", "TRST",
|
|
)
|
|
_parameters = (
|
|
"ENDSTATE", "HZ", "MASK", "MAXIMUM", "SCK", "SEC", "SMASK", "TCK", "TDI", "TDO",
|
|
)
|
|
_trst_modes = (
|
|
"ON", "OFF", "Z", "ABSENT"
|
|
)
|
|
_tap_states = (
|
|
"RESET", "IDLE", "DRSELECT", "DRCAPTURE", "DRSHIFT", "DREXIT1", "DRPAUSE",
|
|
"DREXIT2", "DRUPDATE", "IRSELECT", "IRCAPTURE", "IRSHIFT", "IREXIT1", "IRPAUSE",
|
|
"IREXIT2", "IRUPDATE",
|
|
)
|
|
_tap_stable_states = (
|
|
"RESET", "IDLE", "IRPAUSE", "DRPAUSE"
|
|
)
|
|
|
|
|
|
class SVFParsingError(Exception):
|
|
pass
|
|
|
|
|
|
class SVFLexer:
|
|
"""
|
|
A Serial Vector Format lexer.
|
|
|
|
Comments (``! comment``, ``// comment``) are ignored.
|
|
|
|
The following tokens are recognized:
|
|
* Keyword (``HIR``, ``SIR``, ``TIO``, ..., ``;``), returned as Python ``str``;
|
|
* Integer (``8``, ``16``, ...), returned as Python ``int``;
|
|
* Real (``1E0``, ``1E+0``, ``1E-0``, ...), returned as Python ``float``;
|
|
* Bit array (``(0)``, ``(1234)``, ``(F00F)``, ...), returned as Python ``bitarray``;
|
|
* Literal (``(HLUDXZHHLL)``, ``(IN FOO)``, ...), returned as Python ``tuple(str,)``;
|
|
* End of file, returned as Python ``None``.
|
|
|
|
:type buffer: str
|
|
:attr buffer:
|
|
Input buffer.
|
|
|
|
:type position: int
|
|
:attr position:
|
|
Offset into buffer from which the next token will be read.
|
|
"""
|
|
|
|
_keywords = _commands + _parameters + _trst_modes + _tap_states + (";",)
|
|
_scanner = tuple((re.compile(src, re.A|re.I|re.M), act) for src, act in (
|
|
(r"\s+",
|
|
None),
|
|
(r"(?:!|//)([^\n]*)(?:\n|\Z)",
|
|
None),
|
|
(r"({})(?=\s+|[;()]|\Z)".format("|".join(_keywords)),
|
|
lambda m: m[1]),
|
|
(r"(\d+)(?=[^0-9\.E])",
|
|
lambda m: int(m[1])),
|
|
(r"(\d+(?:\.\d+)?(?:E[+-]?\d+)?)",
|
|
lambda m: float(m[1])),
|
|
(r"\(\s*([0-9A-F\s]+)\s*\)",
|
|
lambda m: _hex_to_bitarray(re.sub(r"\s+", "", m[1]))),
|
|
(r"\(\s*(.+?)\s*\)",
|
|
lambda m: (m[1],)),
|
|
(r"\Z",
|
|
lambda m: None),
|
|
))
|
|
|
|
def __init__(self, buffer):
|
|
self.buffer = buffer
|
|
self.position = 0
|
|
|
|
def line_column(self, position=None):
|
|
"""
|
|
Return a ``(line, column)`` tuple for the given or, if not specified, current position.
|
|
|
|
Both the line and the column start at 1.
|
|
"""
|
|
line = len(re.compile(r"\n").findall(self.buffer, endpos=self.position))
|
|
if line > 1:
|
|
column = self.position - self.buffer.rindex("\n", 0, self.position)
|
|
else:
|
|
column = self.position
|
|
return line + 1, column + 1
|
|
|
|
def _lex(self):
|
|
while True:
|
|
for token_re, action in self._scanner:
|
|
match = token_re.match(self.buffer, self.position)
|
|
# print(token_re, match)
|
|
if match:
|
|
if action is None:
|
|
self.position = match.end()
|
|
break
|
|
else:
|
|
return action(match), match.end()
|
|
else:
|
|
raise SVFParsingError("unrecognized SVF data at line %d, column %d (%s...)"
|
|
% (*self.line_column(),
|
|
self.buffer[self.position:self.position + 16]))
|
|
|
|
def peek(self):
|
|
"""Return the next token without advancing the position."""
|
|
token, _ = self._lex()
|
|
return token
|
|
|
|
def next(self):
|
|
"""Return the next token and advance the position."""
|
|
token, next_pos = self._lex()
|
|
self.position = next_pos
|
|
return token
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
token = self.next()
|
|
if token is None:
|
|
raise StopIteration
|
|
return token
|
|
|
|
|
|
class SVFParser:
|
|
"""
|
|
A Serial Vector Format streaming parser.
|
|
|
|
This parser maintains and allows querying lexical state (e.g. "sticky" ``TDI`` is
|
|
automatically tracked), and invokes the SVF event handler for all commands so that
|
|
any necessary action may be taken.
|
|
"""
|
|
def __init__(self, buffer, handler):
|
|
self._lexer = SVFLexer(buffer)
|
|
self._handler = handler
|
|
self._position = 0
|
|
self._token = None
|
|
self._cmd_pos = 0
|
|
|
|
self._param_tdi = \
|
|
{"HIR": None, "HDR": None, "SIR": None, "SDR": None, "TIR": None, "TDR": None}
|
|
self._param_mask = \
|
|
{"HIR": None, "HDR": None, "SIR": None, "SDR": None, "TIR": None, "TDR": None}
|
|
self._param_smask = \
|
|
{"HIR": None, "HDR": None, "SIR": None, "SDR": None, "TIR": None, "TDR": None}
|
|
|
|
self._param_run_state = "IDLE"
|
|
self._param_end_state = "IDLE"
|
|
|
|
def _try(self, action, *args):
|
|
try:
|
|
old_position = self._lexer.position
|
|
return action(*args)
|
|
except SVFParsingError as e:
|
|
self._lexer.position = old_position
|
|
return None
|
|
|
|
def _parse_token(self):
|
|
self._position = self._lexer.position
|
|
self._token = self._lexer.next()
|
|
# print("token %s @ %d" % (self._token, self._position))
|
|
return self._token
|
|
|
|
def _parse_error(self, error):
|
|
raise SVFParsingError("%s at line %d, column %d"
|
|
% (error, *self._lexer.line_column(self._position)))
|
|
|
|
def _parse_unexpected(self, expected, valid=()):
|
|
if isinstance(self._token, str):
|
|
actual = self._token
|
|
elif isinstance(self._token, int):
|
|
actual = "integer"
|
|
elif isinstance(self._token, float):
|
|
actual = "real"
|
|
elif isinstance(self._token, bitarray):
|
|
actual = "scan data"
|
|
elif isinstance(self._token, tuple):
|
|
actual = "(%s)" % (*self._token,)
|
|
elif self._token is None:
|
|
actual = "end of file"
|
|
else:
|
|
assert False
|
|
if valid:
|
|
self._parse_error("expected %s (one of %s), found %s"
|
|
% (expected, ", ".join(valid), actual))
|
|
else:
|
|
self._parse_error("expected %s, found %s"
|
|
% (expected, actual))
|
|
|
|
def _parse_keyword(self, keyword):
|
|
if self._parse_token() == keyword:
|
|
return self._token
|
|
else:
|
|
self._parse_unexpected("semicolon" if keyword == ";" else keyword)
|
|
|
|
def _parse_keywords(self, keywords):
|
|
if self._parse_token() in keywords:
|
|
return self._token
|
|
else:
|
|
self._parse_unexpected("one of {}".format(", ".join(keywords)))
|
|
|
|
def _parse_value(self, kind):
|
|
if isinstance(self._parse_token(), kind):
|
|
return self._token
|
|
else:
|
|
if kind == int:
|
|
expected = "integer"
|
|
elif kind == float:
|
|
expected = "real"
|
|
elif kind == (int, float):
|
|
expected = "number"
|
|
elif kind == bitarray:
|
|
expected = "scan data"
|
|
elif kind == tuple:
|
|
expected = "data"
|
|
else:
|
|
assert False
|
|
self._parse_unexpected(expected)
|
|
|
|
def _parse_trst_mode(self):
|
|
if self._parse_token() in _trst_modes:
|
|
return self._token
|
|
else:
|
|
self._parse_unexpected("TRST mode", _trst_modes)
|
|
|
|
def _parse_tap_state(self):
|
|
if self._parse_token() in _tap_states:
|
|
return self._token
|
|
else:
|
|
self._parse_unexpected("TAP state", _tap_states)
|
|
|
|
def _parse_tap_stable_state(self):
|
|
if self._parse_token() in _tap_stable_states:
|
|
return self._token
|
|
else:
|
|
self._parse_unexpected("stable TAP state", _tap_stable_states)
|
|
|
|
def _parse_scan_data(self, length):
|
|
value = self._parse_value(bitarray)
|
|
if value[length:].count(1) != 0:
|
|
residue = value[length:]
|
|
residue.reverse()
|
|
self._parse_error("scan data length %d exceeds command length %d"
|
|
% (len(value), length))
|
|
|
|
if length > len(value):
|
|
padding = bitarray(length - len(value), endian="little")
|
|
padding.setall(0)
|
|
value.extend(padding)
|
|
return value
|
|
else:
|
|
return value[:length]
|
|
|
|
def parse_command(self):
|
|
self._cmd_pos = self._lexer.position
|
|
|
|
command = self._parse_token()
|
|
if command is None:
|
|
return False
|
|
|
|
elif command == "FREQUENCY":
|
|
cycles = self._try(self._parse_value, (int, float))
|
|
if cycles is not None:
|
|
self._parse_keyword("HZ")
|
|
self._parse_keyword(";")
|
|
|
|
result = self._handler.svf_frequency(frequency=cycles)
|
|
|
|
elif command == "TRST":
|
|
mode = self._parse_trst_mode()
|
|
self._parse_keyword(";")
|
|
|
|
result = self._handler.svf_trst(mode=mode)
|
|
|
|
elif command == "STATE":
|
|
states = []
|
|
while True:
|
|
state = self._try(self._parse_tap_state)
|
|
if state is None: break
|
|
states.append(state)
|
|
|
|
self._parse_keyword(";")
|
|
|
|
if not states:
|
|
self._parse_error("at least one state required")
|
|
if states[-1] not in _tap_stable_states:
|
|
self._parse_error("last state must be a stable state")
|
|
|
|
*path_states, stable_state = states
|
|
result = self._handler.svf_state(state=stable_state, path=path_states)
|
|
|
|
elif command in ("ENDIR", "ENDDR"):
|
|
stable_state = self._parse_tap_stable_state()
|
|
self._parse_keyword(";")
|
|
|
|
if command == "ENDIR":
|
|
result = self._handler.svf_endir(state=stable_state)
|
|
if command == "ENDDR":
|
|
result = self._handler.svf_enddr(state=stable_state)
|
|
|
|
elif command in ("HIR", "SIR", "TIR", "HDR", "SDR", "TDR"):
|
|
length = self._parse_value(int)
|
|
|
|
if self._param_mask[command] is None or len(self._param_mask[command]) != length:
|
|
self._param_mask[command] = bitarray(length, endian="little")
|
|
self._param_mask[command].setall(1)
|
|
if self._param_smask[command] is None or len(self._param_smask[command]) != length:
|
|
self._param_smask[command] = bitarray(length, endian="little")
|
|
self._param_smask[command].setall(1)
|
|
|
|
param_tdi = self._param_tdi[command]
|
|
param_tdo = None
|
|
param_mask = self._param_mask[command]
|
|
param_smask = self._param_smask[command]
|
|
parameters = set()
|
|
while True:
|
|
parameter = self._try(self._parse_keywords, ("TDI", "TDO", "MASK", "SMASK"))
|
|
if parameter is None: break
|
|
|
|
value = self._parse_scan_data(length)
|
|
if parameter in parameters:
|
|
self._parse_error("parameter %s specified twice" % parameter)
|
|
parameters.add(parameter)
|
|
|
|
if parameter == "TDI":
|
|
self._param_tdi[command] = value
|
|
param_tdi = value
|
|
if parameter == "TDO":
|
|
param_tdo = value
|
|
if parameter == "MASK":
|
|
self._param_mask[command] = value
|
|
param_mask = value
|
|
if parameter == "SMASK":
|
|
self._param_smask[command] = value
|
|
param_smask = value
|
|
|
|
self._parse_keyword(";")
|
|
|
|
if param_tdi is None and length == 0:
|
|
param_tdi = bitarray("", endian="little")
|
|
elif param_tdi is None:
|
|
self._parse_error("initial value for parameter TDI required")
|
|
if len(param_tdi) != length:
|
|
self._parse_error("parameter TDI needs to be specified again because "
|
|
"the length changed")
|
|
|
|
if param_tdo is None:
|
|
# Make it a bit easier for downstream; set MASK (but not remembered MASK)
|
|
# to "all don't care" if there's no TDO specified.
|
|
param_mask = bitarray(param_mask)
|
|
param_mask.setall(0)
|
|
|
|
if command == "HIR":
|
|
result = self._handler.svf_hir(tdi=param_tdi, smask=param_smask,
|
|
tdo=param_tdo, mask=param_mask)
|
|
if command == "SIR":
|
|
result = self._handler.svf_sir(tdi=param_tdi, smask=param_smask,
|
|
tdo=param_tdo, mask=param_mask)
|
|
if command == "TIR":
|
|
result = self._handler.svf_tir(tdi=param_tdi, smask=param_smask,
|
|
tdo=param_tdo, mask=param_mask)
|
|
if command == "HDR":
|
|
result = self._handler.svf_hdr(tdi=param_tdi, smask=param_smask,
|
|
tdo=param_tdo, mask=param_mask)
|
|
if command == "SDR":
|
|
result = self._handler.svf_sdr(tdi=param_tdi, smask=param_smask,
|
|
tdo=param_tdo, mask=param_mask)
|
|
if command == "TDR":
|
|
result = self._handler.svf_tdr(tdi=param_tdi, smask=param_smask,
|
|
tdo=param_tdo, mask=param_mask)
|
|
|
|
elif command == "RUNTEST":
|
|
run_state = self._try(self._parse_tap_stable_state)
|
|
run_params = self._try(lambda:
|
|
(self._parse_value(int), self._parse_keywords(("TCK", "SCK"))))
|
|
if run_params is None:
|
|
run_count, run_clock = None, "TCK"
|
|
min_time, _ = \
|
|
self._parse_value((int, float)), self._parse_keyword("SEC")
|
|
else:
|
|
run_count, run_clock = run_params
|
|
min_time, _ = self._try(lambda:
|
|
(self._parse_value((int, float)), self._parse_keyword("SEC"))) \
|
|
or (None, None)
|
|
if self._try(self._parse_keyword, "MAXIMUM"):
|
|
max_time, _ = \
|
|
self._parse_value((int, float)), self._parse_keyword("SEC")
|
|
else:
|
|
max_time = None
|
|
if self._try(self._parse_keyword, "ENDSTATE"):
|
|
end_state = self._parse_tap_stable_state()
|
|
else:
|
|
end_state = None
|
|
self._parse_keyword(";")
|
|
|
|
if run_state is None:
|
|
run_state = self._param_run_state
|
|
else:
|
|
self._param_run_state = run_state
|
|
if end_state is None:
|
|
end_state = run_state
|
|
|
|
if end_state is None:
|
|
end_state = self._param_end_state
|
|
else:
|
|
self._param_end_state = end_state
|
|
|
|
if run_clock is None:
|
|
run_clock = "TCK"
|
|
|
|
if max_time is not None and min_time is not None and max_time < min_time:
|
|
self._parse_error("maximum time must be greater than minimum time")
|
|
|
|
result = self._handler.svf_runtest(run_state=run_state,
|
|
run_count=run_count, run_clock=run_clock,
|
|
min_time =min_time, max_time=max_time,
|
|
end_state=end_state)
|
|
|
|
elif command == "PIOMAP":
|
|
mapping, = self._parse_value(tuple)
|
|
self._parse_keyword(";")
|
|
|
|
result = self._handler.svf_piomap(mapping=mapping)
|
|
|
|
elif command == "PIO":
|
|
vector, = self._parse_value(tuple)
|
|
self._parse_keyword(";")
|
|
|
|
result = self._handler.svf_pio(vector=vector)
|
|
|
|
else:
|
|
self._parse_unexpected("command", _commands)
|
|
|
|
return result or True
|
|
|
|
def last_command(self):
|
|
return self._lexer.buffer[self._cmd_pos:self._lexer.position]
|
|
|
|
def parse_file(self):
|
|
while self.parse_command(): pass
|
|
|
|
|
|
class SVFEventHandler(metaclass=ABCMeta):
|
|
"""
|
|
An abstract base class for Serial Vector Format parsing events.
|
|
|
|
The methods of this class are called when a well-formed SVF command is encountered.
|
|
The parser takes care of maintaining all lexical state (e.g. "sticky" parameters),
|
|
but all logical state is maintained by the event handler.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def svf_frequency(self, frequency):
|
|
"""Called when the ``FREQUENCY`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_trst(self, mode):
|
|
"""Called when the ``TRST`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_state(self, state, path):
|
|
"""Called when the ``STATE`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_endir(self, state):
|
|
"""Called when the ``ENDIR`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_enddr(self, state):
|
|
"""Called when the ``ENDDR`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_hir(self, tdi, smask, tdo, mask):
|
|
"""Called when the ``HIR`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_sir(self, tdi, smask, tdo, mask):
|
|
"""Called when the ``SIR`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_tir(self, tdi, smask, tdo, mask):
|
|
"""Called when the ``TIR`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_hdr(self, tdi, smask, tdo, mask):
|
|
"""Called when the ``HDR`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_sdr(self, tdi, smask, tdo, mask):
|
|
"""Called when the ``SDR`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_tdr(self, tdi, smask, tdo, mask):
|
|
"""Called when the ``TDR`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_runtest(self, run_state, run_count, run_clock, min_time, max_time, end_state):
|
|
"""Called when the ``RUNTEST`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_piomap(self, mapping):
|
|
"""Called when the ``PIOMAP`` command is encountered."""
|
|
|
|
@abstractmethod
|
|
def svf_pio(self, vector):
|
|
"""Called when the ``PIO`` command is encountered."""
|
|
|
|
# -------------------------------------------------------------------------------------------------
|
|
|
|
import unittest
|
|
|
|
|
|
class SVFLexerTestCase(unittest.TestCase):
|
|
def assertLexes(self, source, tokens):
|
|
self.lexer = SVFLexer(source)
|
|
self.assertEqual(list(self.lexer), tokens)
|
|
|
|
def test_eof(self):
|
|
self.assertLexes("", [])
|
|
|
|
def test_comment(self):
|
|
self.assertLexes("!foo",
|
|
[])
|
|
self.assertLexes("//foo",
|
|
[])
|
|
self.assertLexes("//foo\n!bar\n",
|
|
[])
|
|
self.assertLexes("//foo\n!bar\nTRST",
|
|
["TRST"])
|
|
|
|
def test_keyword(self):
|
|
self.assertLexes("TRST",
|
|
["TRST"])
|
|
self.assertLexes("TRST OFF;",
|
|
["TRST", "OFF", ";"])
|
|
|
|
def test_integer(self):
|
|
self.assertLexes("8", [8])
|
|
self.assertLexes("12", [12])
|
|
|
|
def test_real(self):
|
|
self.assertLexes("1E6", [1e6])
|
|
self.assertLexes("1E+6", [1e6])
|
|
self.assertLexes("1E-6", [1e-6])
|
|
self.assertLexes("1.1E6", [1.1e6])
|
|
self.assertLexes("1.1", [1.1])
|
|
|
|
def test_bitarray(self):
|
|
self.assertLexes("(0)", [bitarray("00000000")])
|
|
self.assertLexes("(1)", [bitarray("10000000")])
|
|
self.assertLexes("(F)", [bitarray("11110000")])
|
|
self.assertLexes("(f)", [bitarray("11110000")])
|
|
self.assertLexes("(0F)", [bitarray("11110000")])
|
|
self.assertLexes("(A\n5)", [bitarray("10100101")]) # Test literals split over two lines
|
|
self.assertLexes("(A\n\t5)", [bitarray("10100101")]) # With potential whitespace
|
|
self.assertLexes("(A\n 5)", [bitarray("10100101")])
|
|
self.assertLexes("(A\r\n5)", [bitarray("10100101")]) # Support both LF & LFCR
|
|
self.assertLexes("(A\r\n\t5)", [bitarray("10100101")])
|
|
self.assertLexes("(A\r\n 5)", [bitarray("10100101")])
|
|
self.assertLexes("(FF)", [bitarray("11111111")])
|
|
self.assertLexes("(1AA)", [bitarray("0101010110000000")])
|
|
|
|
def test_literal(self):
|
|
self.assertLexes("(HHZZL)", [("HHZZL",)])
|
|
self.assertLexes("(IN FOO)", [("IN FOO",)])
|
|
|
|
def test_error(self):
|
|
with self.assertRaises(SVFParsingError):
|
|
SVFLexer("XXX").next()
|
|
|
|
|
|
class SVFMockEventHandler:
|
|
def __init__(self):
|
|
self.events = []
|
|
|
|
def __getattr__(self, name):
|
|
if name.startswith("svf_"):
|
|
def svf_event(**kwargs):
|
|
self.events.append((name, kwargs))
|
|
return svf_event
|
|
else:
|
|
return super().__getattr__(name)
|
|
|
|
|
|
class SVFParserTestCase(unittest.TestCase):
|
|
def setUp(self):
|
|
self.maxDiff = None
|
|
|
|
def assertParses(self, source, events):
|
|
self.handler = SVFMockEventHandler()
|
|
self.parser = SVFParser(source, self.handler)
|
|
self.parser.parse_file()
|
|
self.assertEqual(self.handler.events, events)
|
|
|
|
def assertErrors(self, source, error):
|
|
with self.assertRaisesRegex(SVFParsingError, r"^{}".format(re.escape(error))):
|
|
self.handler = SVFMockEventHandler()
|
|
self.parser = SVFParser(source, self.handler)
|
|
self.parser.parse_file()
|
|
|
|
def test_frequency(self):
|
|
self.assertParses("FREQUENCY;",
|
|
[("svf_frequency", {"frequency": None})])
|
|
self.assertParses("FREQUENCY 1E6 HZ;",
|
|
[("svf_frequency", {"frequency": 1e6})])
|
|
self.assertParses("FREQUENCY 1000 HZ;",
|
|
[("svf_frequency", {"frequency": 1000})])
|
|
|
|
self.assertErrors("FREQUENCY 1E6;",
|
|
"expected HZ")
|
|
|
|
def test_trst(self):
|
|
self.assertParses("TRST ON;",
|
|
[("svf_trst", {"mode": "ON"})])
|
|
self.assertParses("TRST OFF;",
|
|
[("svf_trst", {"mode": "OFF"})])
|
|
self.assertParses("TRST Z;",
|
|
[("svf_trst", {"mode": "Z"})])
|
|
self.assertParses("TRST ABSENT;",
|
|
[("svf_trst", {"mode": "ABSENT"})])
|
|
|
|
self.assertErrors("TRST HZ;",
|
|
"expected TRST mode")
|
|
|
|
def test_state(self):
|
|
self.assertParses("STATE IDLE;",
|
|
[("svf_state", {"state": "IDLE", "path": []})])
|
|
self.assertParses("STATE IRUPDATE IDLE;",
|
|
[("svf_state", {"state": "IDLE", "path": ["IRUPDATE"]})])
|
|
self.assertParses("STATE IREXIT2 IRUPDATE IDLE;",
|
|
[("svf_state", {"state": "IDLE", "path": ["IREXIT2", "IRUPDATE"]})])
|
|
|
|
self.assertErrors("STATE;",
|
|
"at least one state required")
|
|
self.assertErrors("STATE IRSHIFT;",
|
|
"last state must be a stable state")
|
|
self.assertErrors("STATE RESET IRSHIFT;",
|
|
"last state must be a stable state")
|
|
|
|
def test_endir_enddr(self):
|
|
for command, event in [
|
|
("ENDIR", "svf_endir"),
|
|
("ENDDR", "svf_enddr")
|
|
]:
|
|
self.assertParses("{c} IRPAUSE;".format(c=command),
|
|
[(event, {"state": "IRPAUSE"})])
|
|
|
|
self.assertErrors("{c} IRSHIFT;".format(c=command),
|
|
"expected stable TAP state")
|
|
self.assertErrors("{c};".format(c=command),
|
|
"expected stable TAP state")
|
|
|
|
def test_hir_sir_tir_hdr_sdr_tdr(self):
|
|
for command, event in [
|
|
("HIR", "svf_hir"),
|
|
("SIR", "svf_sir"),
|
|
("TIR", "svf_tir"),
|
|
("HDR", "svf_hdr"),
|
|
("SDR", "svf_sdr"),
|
|
("TDR", "svf_tdr"),
|
|
]:
|
|
self.assertParses("{c} 0;".format(c=command), [
|
|
(event, {
|
|
"tdi": bitarray(""),
|
|
"smask": bitarray(""),
|
|
"tdo": None,
|
|
"mask": bitarray(""),
|
|
}),
|
|
])
|
|
self.assertParses("{c} 8 TDI(a);".format(c=command), [
|
|
(event, {
|
|
"tdi": bitarray("01010000"),
|
|
"smask": bitarray("11111111"),
|
|
"tdo": None,
|
|
"mask": bitarray("00000000"),
|
|
}),
|
|
])
|
|
self.assertParses("{c} 6 TDI(0a);".format(c=command), [
|
|
(event, {
|
|
"tdi": bitarray("010100"),
|
|
"smask": bitarray("111111"),
|
|
"tdo": None,
|
|
"mask": bitarray("000000"),
|
|
}),
|
|
])
|
|
self.assertParses("{c} 8 TDI(a); {c} 8;".format(c=command), [
|
|
(event, {
|
|
"tdi": bitarray("01010000"),
|
|
"smask": bitarray("11111111"),
|
|
"tdo": None,
|
|
"mask": bitarray("00000000"),
|
|
}),
|
|
(event, {
|
|
"tdi": bitarray("01010000"),
|
|
"smask": bitarray("11111111"),
|
|
"tdo": None,
|
|
"mask": bitarray("00000000"),
|
|
}),
|
|
])
|
|
self.assertParses("{c} 8 TDI(a) SMASK(3); {c} 8; {c} 12 TDI(b);"
|
|
.format(c=command), [
|
|
(event, {
|
|
"tdi": bitarray("01010000"),
|
|
"smask": bitarray("11000000"),
|
|
"tdo": None,
|
|
"mask": bitarray("00000000"),
|
|
}),
|
|
(event, {
|
|
"tdi": bitarray("01010000"),
|
|
"smask": bitarray("11000000"),
|
|
"tdo": None,
|
|
"mask": bitarray("00000000"),
|
|
}),
|
|
(event, {
|
|
"tdi": bitarray("110100000000"),
|
|
"smask": bitarray("111111111111"),
|
|
"tdo": None,
|
|
"mask": bitarray("000000000000"),
|
|
}),
|
|
])
|
|
self.assertParses("{c} 8 TDI(0) TDO(a) MASK(3); {c} 8; "
|
|
"{c} 8 TDO(1); {c} 12 TDI(0) TDO(b);"
|
|
.format(c=command), [
|
|
(event, {
|
|
"tdi": bitarray("00000000"),
|
|
"smask": bitarray("11111111"),
|
|
"tdo": bitarray("01010000"),
|
|
"mask": bitarray("11000000"),
|
|
}),
|
|
(event, {
|
|
"tdi": bitarray("00000000"),
|
|
"smask": bitarray("11111111"),
|
|
"tdo": None,
|
|
"mask": bitarray("00000000"),
|
|
}),
|
|
(event, {
|
|
"tdi": bitarray("00000000"),
|
|
"smask": bitarray("11111111"),
|
|
"tdo": bitarray("10000000"),
|
|
"mask": bitarray("11000000"),
|
|
}),
|
|
(event, {
|
|
"tdi": bitarray("000000000000"),
|
|
"smask": bitarray("111111111111"),
|
|
"tdo": bitarray("110100000000"),
|
|
"mask": bitarray("111111111111"),
|
|
}),
|
|
])
|
|
|
|
self.assertErrors("{c} 8 TDI(aaa);".format(c=command),
|
|
"scan data length 12 exceeds command length 8")
|
|
self.assertErrors("{c} 8 TDI(0) TDI(0);".format(c=command),
|
|
"parameter TDI specified twice")
|
|
self.assertErrors("{c} 8;".format(c=command),
|
|
"initial value for parameter TDI required")
|
|
self.assertErrors("{c} 8 TDI(aa); {c} 12;".format(c=command),
|
|
"parameter TDI needs to be specified again because "
|
|
"the length changed")
|
|
|
|
def test_runtest(self):
|
|
self.assertParses("RUNTEST 20000 TCK;", [
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
|
|
"min_time": None, "max_time": None, "end_state": "IDLE"
|
|
}),
|
|
])
|
|
self.assertParses("RUNTEST 20000 TCK 1E3 SEC;", [
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
|
|
"min_time": 1e3, "max_time": None, "end_state": "IDLE"
|
|
}),
|
|
])
|
|
self.assertParses("RUNTEST 20000 TCK 1E3 SEC MAXIMUM 1E6 SEC;", [
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
|
|
"min_time": 1e3, "max_time": 1e6, "end_state": "IDLE"
|
|
}),
|
|
])
|
|
self.assertParses("RUNTEST 20000 TCK 1E3 SEC MAXIMUM 1E6 SEC ENDSTATE RESET;", [
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
|
|
"min_time": 1e3, "max_time": 1e6, "end_state": "RESET"
|
|
}),
|
|
])
|
|
self.assertParses("RUNTEST 20000 TCK 1E3 SEC MAXIMUM 1E6 SEC ENDSTATE RESET;", [
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
|
|
"min_time": 1e3, "max_time": 1e6, "end_state": "RESET"
|
|
}),
|
|
])
|
|
self.assertParses("RUNTEST 20000 TCK ENDSTATE RESET; RUNTEST 100 TCK;", [
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": 20000, "run_clock": "TCK",
|
|
"min_time": None, "max_time": None, "end_state": "RESET"
|
|
}),
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": 100, "run_clock": "TCK",
|
|
"min_time": None, "max_time": None, "end_state": "RESET"
|
|
}),
|
|
])
|
|
self.assertParses("RUNTEST RESET 20000 TCK ENDSTATE RESET; RUNTEST IDLE 100 TCK;", [
|
|
("svf_runtest", {
|
|
"run_state": "RESET", "run_count": 20000, "run_clock": "TCK",
|
|
"min_time": None, "max_time": None, "end_state": "RESET"
|
|
}),
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": 100, "run_clock": "TCK",
|
|
"min_time": None, "max_time": None, "end_state": "IDLE"
|
|
}),
|
|
])
|
|
|
|
self.assertParses("RUNTEST 20000 SCK;", [
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": 20000, "run_clock": "SCK",
|
|
"min_time": None, "max_time": None, "end_state": "IDLE"
|
|
}),
|
|
])
|
|
|
|
self.assertParses("RUNTEST 1 SEC;", [
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": None, "run_clock": "TCK",
|
|
"min_time": 1, "max_time": None, "end_state": "IDLE"
|
|
}),
|
|
])
|
|
self.assertParses("RUNTEST 1 SEC MAXIMUM 2 SEC;", [
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": None, "run_clock": "TCK",
|
|
"min_time": 1, "max_time": 2, "end_state": "IDLE"
|
|
}),
|
|
])
|
|
self.assertParses("RUNTEST 200 TCK ENDSTATE RESET; RUNTEST 1 SEC;", [
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": 200, "run_clock": "TCK",
|
|
"min_time": None, "max_time": None, "end_state": "RESET"
|
|
}),
|
|
("svf_runtest", {
|
|
"run_state": "IDLE", "run_count": None, "run_clock": "TCK",
|
|
"min_time": 1, "max_time": None, "end_state": "RESET"
|
|
}),
|
|
])
|
|
|
|
self.assertErrors("RUNTEST;",
|
|
"expected number")
|
|
self.assertErrors("RUNTEST 2 SEC MAXIMUM 1 SEC;",
|
|
"maximum time must be greater than minimum time")
|
|
|
|
def test_piomap(self):
|
|
self.assertParses("PIOMAP (IN FOO OUT BAR);",
|
|
[("svf_piomap", {"mapping": "IN FOO OUT BAR"})])
|
|
|
|
self.assertErrors("PIOMAP;",
|
|
"expected data")
|
|
|
|
def test_pio(self):
|
|
self.assertParses("PIO (LHZX);",
|
|
[("svf_pio", {"vector": "LHZX"})])
|
|
|
|
self.assertErrors("PIO;",
|
|
"expected data")
|
|
|
|
def test_last_command(self):
|
|
handler = SVFMockEventHandler()
|
|
parser = SVFParser(" TRST OFF; SIR 8 TDI (aa); ", handler)
|
|
parser.parse_command()
|
|
self.assertEqual(parser.last_command(), " TRST OFF;")
|
|
parser.parse_command()
|
|
self.assertEqual(parser.last_command(), " SIR 8 TDI (aa);")
|
|
|
|
# -------------------------------------------------------------------------------------------------
|
|
|
|
class SVFPrintingEventHandler:
|
|
def __getattr__(self, name):
|
|
if name.startswith("svf_"):
|
|
def svf_event(**kwargs):
|
|
print((name, kwargs))
|
|
return svf_event
|
|
else:
|
|
return super().__getattr__(name)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
with open(sys.argv[1]) as f:
|
|
SVFParser(f.read(), SVFPrintingEventHandler()).parse_file()
|