# 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()