Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 08d1f887887f
Choose a base ref
...
head repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: ab9dc169e887
Choose a head ref
  • 1 commit
  • 2 files changed
  • 1 contributor

Commits on Mar 10, 2019

  1. protocol.jesd3: new protocol.

    Based on a parser by @marcan.
    whitequark committed Mar 10, 2019
    Copy the full SHA
    ab9dc16 View commit details
Showing with 297 additions and 1 deletion.
  1. +1 −1 docs/archive
  2. +296 −0 software/glasgow/protocol/jesd3.py
2 changes: 1 addition & 1 deletion docs/archive
296 changes: 296 additions & 0 deletions software/glasgow/protocol/jesd3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
# Ref: JEDEC JESD3-C
# Accession: G00029

import re
from bitarray import bitarray


__all__ = ["JEDParser"]


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"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]*", 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(values.replace(" ", ""), 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)
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 not 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=True)
parser.parse()
for i in range(0, len(parser.fuse) + 63, 64):
print("%08x: %s" % (i, parser.fuse[i:i + 64].to01()))