mirror of
https://github.com/ole00/afterburner.git
synced 2024-11-28 15:49:49 +00:00
301 lines
10 KiB
Python
301 lines
10 KiB
Python
# Vendored from glasgow.protocol.jesd3
|
|
|
|
# Ref: JEDEC JESD3-C
|
|
# Accession: G00029
|
|
|
|
import re
|
|
from bitarray import bitarray
|
|
|
|
|
|
__all__ = ["JESD3Parser", "JESD3ParsingError"]
|
|
|
|
|
|
class JESD3ParsingError(Exception):
|
|
pass
|
|
|
|
|
|
class JESD3Lexer:
|
|
"""
|
|
A JESD3 (JED) lexer.
|
|
|
|
:type buffer: str
|
|
:attr buffer:
|
|
Input buffer.
|
|
|
|
:type position: int
|
|
:attr position:
|
|
Offset into buffer from which the next field will be read.
|
|
"""
|
|
|
|
# This follows the JESD3-C grammar, with the exception that spaces are more permissive.
|
|
# As described, only 0x0D is allowed in between fields, which is absurd.
|
|
_fields = (
|
|
(r"N", r"[ \r\n]*(.*?)"),
|
|
(r"D", r".*?"),
|
|
(r"QF", r"([0-9]+)"),
|
|
(r"QP", r"([0-9]+)"),
|
|
(r"QV", r"([0-9]+)"),
|
|
(r"F", r"([01])"),
|
|
(r"L", r"([0-9]+)[ \r\n]+([01 \r\n]+)"),
|
|
(r"C", r"([0-9A-F]{4})"),
|
|
(r"EH", r"([0-9A-F]+)"),
|
|
(r"E", r"([01]+)"),
|
|
(r"UA", r"([\r\n\x20-\x29\x2B-\x7E]+)"),
|
|
(r"UH", r"([0-9A-F]+)"),
|
|
(r"U", r"([01]+)"),
|
|
(r"J", r"([0-9]+)[ \r\n]+([0-9]+)"),
|
|
(r"G", r"([01])"),
|
|
(r"X", r"([01])"),
|
|
(r"P", r"([ \r\n]*[0-9]+)+"),
|
|
(r"V", r"([0-9]+)[ \r\n]+([0-9BCDFHTUXZ]+)"),
|
|
(r"S", r"([01]+)"),
|
|
(r"R", r"([0-9A-F]{8})"),
|
|
(r"T", r"([0-9]+)"),
|
|
(r"A", r"([\r\n\x20-\x29\x2B-\x7E]*)([0-9]+)"),
|
|
)
|
|
_stx_spec_re = re.compile(r"\x02(.*?)\*[ \r\n]*", re.A|re.S)
|
|
_stx_quirk_re = re.compile(r"\x02()[ \r\n]*", re.A|re.S)
|
|
_etx_re = re.compile(r"\x03([0-9A-F]{4})", re.A|re.S)
|
|
_ident_re = re.compile(r"|".join(ident for ident, args in _fields), re.A|re.S)
|
|
_field_res = {ident: re.compile(ident + args + r"[ \r\n]*\*[ \r\n]*", re.A|re.S)
|
|
for ident, args in _fields}
|
|
|
|
def __init__(self, buffer, quirk_no_design_spec=False):
|
|
self.buffer = buffer
|
|
self.position = 0
|
|
self.checksum = 0
|
|
self._state = "start"
|
|
if quirk_no_design_spec:
|
|
self._stx_re = self._stx_quirk_re
|
|
else:
|
|
self._stx_re = self._stx_spec_re
|
|
|
|
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 __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
"""Return the next token and advance the position."""
|
|
if self._state == "start":
|
|
match = self._stx_re.search(self.buffer, self.position)
|
|
if not match:
|
|
raise JESD3ParsingError("could not find STX marker")
|
|
else:
|
|
token = "start"
|
|
self._state = "fields"
|
|
self.checksum += sum(map(ord, match.group(0)))
|
|
|
|
elif self._state == "fields":
|
|
match = self._ident_re.match(self.buffer, self.position)
|
|
if match:
|
|
token = match.group(0)
|
|
match = self._field_res[token].match(self.buffer, self.position)
|
|
if not match:
|
|
raise JESD3ParsingError("field %s has invalid format at line %d, column %d"
|
|
% (token, *self.line_column()))
|
|
else:
|
|
self.checksum += sum(map(ord, match.group(0)))
|
|
|
|
else:
|
|
match = self._etx_re.match(self.buffer, self.position)
|
|
if not match:
|
|
raise JESD3ParsingError("unrecognized field at line %d, column %d (%r...)"
|
|
% (*self.line_column(),
|
|
self.buffer[self.position:self.position + 16]))
|
|
else:
|
|
token = "end"
|
|
self._state = "end"
|
|
self.checksum += 0x03
|
|
|
|
elif self._state == "end":
|
|
raise StopIteration
|
|
|
|
self.position = match.end()
|
|
return token, match.start(), match.groups()
|
|
|
|
|
|
class JESD3Parser:
|
|
def __init__(self, buffer, **kwargs):
|
|
self._lexer = JESD3Lexer(buffer, **kwargs)
|
|
self._position = 0
|
|
|
|
self.design_spec = ""
|
|
self.notes = []
|
|
self.fuse = None
|
|
self._fuse_default = None
|
|
self._fuse_bit_count = 0
|
|
self.electrical_fuse = None
|
|
self.user_fuse = None
|
|
self.security_fuse = None
|
|
self.device_id = None
|
|
|
|
def _parse_error(self, error):
|
|
raise JESD3ParsingError("%s at line %d, column %d"
|
|
% (error, *self._lexer.line_column(self._position)))
|
|
|
|
def parse(self):
|
|
for token, position, args in self._lexer:
|
|
self._position = position
|
|
# print("lexem: %r %r" % (token, args))
|
|
getattr(self, "_on_" + token)(*args)
|
|
|
|
def _on_start(self, design_spec):
|
|
"""Start marker and design specification"""
|
|
self.design_spec = design_spec
|
|
|
|
def _on_N(self, note):
|
|
"""Note"""
|
|
self.notes.append(note)
|
|
|
|
def _on_D(self):
|
|
"""Device (obsolete)"""
|
|
|
|
def _on_QF(self, count):
|
|
"""Fuse count"""
|
|
if self.fuse is not None:
|
|
self._parse_error("fuse count specified more than once")
|
|
self.fuse = bitarray(int(count, 10), endian="little")
|
|
|
|
def _on_QP(self, count):
|
|
"""Pin count (unsupported and ignored)"""
|
|
|
|
def _on_QV(self, count):
|
|
"""Test vector count (unsupported)"""
|
|
if int(count, 10) > 0:
|
|
self._parse_error("test vectors are unsupported")
|
|
|
|
def _on_F(self, state):
|
|
"""Fuse default state"""
|
|
if self.fuse is None:
|
|
self._parse_error("fuse default state specified before fuse count")
|
|
if self._fuse_default is not None:
|
|
self._parse_error("fuse default state specified more than once")
|
|
if self._fuse_bit_count > 0:
|
|
self._parse_error("fuse default state specified after fuse list")
|
|
self._fuse_default = int(state, 2)
|
|
self.fuse.setall(self._fuse_default)
|
|
|
|
def _on_L(self, index, values):
|
|
"""Fuse list"""
|
|
if self.fuse is None:
|
|
self._parse_error("fuse list specified before fuse count")
|
|
index = int(index, 10)
|
|
values = bitarray(re.sub(r"[ \r\n]", "", values), endian="little")
|
|
if index + len(values) > len(self.fuse):
|
|
self._parse_error("fuse list specifies range [%d:%d] beyond last fuse %d"
|
|
% (index, index + len(values), len(self.fuse)))
|
|
self.fuse[index:index + len(values)] = values
|
|
self._fuse_bit_count += len(values)
|
|
|
|
def _on_C(self, checksum):
|
|
"""Fuse checksum"""
|
|
expected_checksum = int(checksum, 16)
|
|
actual_checksum = sum(self.fuse.tobytes()) & 0xffff
|
|
if expected_checksum != actual_checksum:
|
|
self._parse_error("fuse checksum mismatch: expected %04X, actual %04X"
|
|
% (expected_checksum, actual_checksum))
|
|
|
|
def _set_electrical_fuse(self, value):
|
|
if self.electrical_fuse is not None:
|
|
self._parse_error("electrical fuse specified more than once")
|
|
self.electrical_fuse = value
|
|
|
|
def _on_EH(self, value):
|
|
"""Electrical fuse, hex"""
|
|
self._set_electrical_fuse(int(value, 16))
|
|
|
|
def _on_E(self, value):
|
|
"""Electrical fuse, binary"""
|
|
self._set_electrical_fuse(int(value, 2))
|
|
|
|
def _set_user_fuse(self, value):
|
|
if self.user_fuse is not None:
|
|
self._parse_error("user fuse specified more than once")
|
|
self.user_fuse = value
|
|
|
|
def _on_UA(self, value):
|
|
"""User fuse, 7-bit ASCII"""
|
|
int_value = 0
|
|
for char in reversed(value):
|
|
int_value <<= 7
|
|
int_value |= ord(char)
|
|
self._set_user_fuse(int_value)
|
|
|
|
def _on_UH(self, value):
|
|
"""User fuse, hex"""
|
|
self._set_user_fuse(int(value, 16))
|
|
|
|
def _on_U(self, value):
|
|
"""User fuse, binary"""
|
|
self._set_user_fuse(int(value, 2))
|
|
|
|
def _on_J(self, arch_code, pinout_code):
|
|
"""Device identification"""
|
|
if self.device_id is not None:
|
|
self._parse_error("device identification specified more than once")
|
|
self.device_id = (int(arch_code, 10), int(pinout_code, 10))
|
|
|
|
def _on_G(self, value):
|
|
"""Security fuse"""
|
|
if self.security_fuse is not None:
|
|
self._parse_error("security fuse specified more than once")
|
|
self.security_fuse = int(value, 2)
|
|
|
|
def _on_X(self, value):
|
|
"""Default test condition (unsupported and ignored)"""
|
|
|
|
def _on_P(self, pin_numbers):
|
|
"""Pin list (unsupported and ignored)"""
|
|
|
|
def _on_V(self, vector_number, test_conditions):
|
|
"""Test vector (unsupported and ignored)"""
|
|
|
|
def _on_S(self, test_condition):
|
|
"""Signature analysis starting vector (unsupported)"""
|
|
self._parse_error("signature analysis is not supported")
|
|
|
|
def _on_R(self, test_sum):
|
|
"""Signature analysis resulting vector (unsupported and ignored)"""
|
|
|
|
def _on_T(self, test_cycles):
|
|
"""Signature analysis test cycle count (unsupported and ignored)"""
|
|
|
|
def _on_A(self, subfield, delay):
|
|
"""Propagation delay for test vectors (unsupported and ignored)"""
|
|
|
|
def _on_end(self, checksum):
|
|
"""End marker and checksum"""
|
|
expected_checksum = int(checksum, 16)
|
|
if expected_checksum == 0x0000:
|
|
return
|
|
actual_checksum = self._lexer.checksum & 0xffff
|
|
if expected_checksum != actual_checksum:
|
|
self._parse_error("transmission checksum mismatch: expected %04X, actual %04X"
|
|
% (expected_checksum, actual_checksum))
|
|
|
|
if self._fuse_default is None and self._fuse_bit_count < len(self.fuse):
|
|
self._parse_error("fuse default state is not specified, and only %d out of %d fuse "
|
|
"bits are explicitly defined"
|
|
% (self._fuse_bit_count, len(self.fuse)))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
with open(sys.argv[1], "r") as f:
|
|
parser = JESD3Parser(f.read(), quirk_no_design_spec=False)
|
|
parser.parse()
|
|
for i in range(0, len(parser.fuse) + 63, 64):
|
|
print("%08x: %s" % (i, parser.fuse[i:i + 64].to01()))
|