added JTAG utils for .jed to .xsvf conversion

This commit is contained in:
ole00 2024-04-12 23:05:03 +01:00
parent 7d20d778c6
commit 18b3cea6a2
7 changed files with 2514 additions and 0 deletions

310
utils/jtag/device.py Normal file
View File

@ -0,0 +1,310 @@
# Be careful modifying this file: line ranges from it are included in docs/jtag/as.rst.
import enum
from bitarray import bitarray
__all__ = ['ATF15xxInstr', 'ATF1502ASDevice', 'ATF1504ASDevice', 'ATF1508ASDevice']
class ATF15xxInstr(enum.IntEnum):
EXTEST = 0x000
SAMPLE = 0x055
IDCODE = 0x059
ISC_READ_UES = 0x270
ISC_CONFIG = 0x280
ISC_READ = 0x28c
ISC_DATA = 0x290
ISC_PROGRAM_ERASE = 0x29e
ISC_ADDRESS = 0x2a1
ISC_LATCH_ERASE = 0x2b3
ISC_UNKNOWN = 0x2bf
BYPASS = 0x3ff
class ATF15xxDevice:
idcode = None # int
fuse_count = None # int
data_width = None # dict(range/tuple,range)
@classmethod
def word_size(cls, svf_row):
for svf_rows, svf_cols in cls.data_width.items():
if svf_row in svf_rows:
return len(svf_cols)
assert False
@staticmethod
def jed_to_svf_coords(jed_index):
raise NotImplementedError
@classmethod
def jed_to_svf(cls, jed_bits):
svf_bits = {}
for jed_index, jed_bit in enumerate(jed_bits):
svf_index = cls.jed_to_svf_coords(jed_index)
if svf_index is None: continue
svf_row, svf_col = svf_index
if svf_row not in svf_bits:
svf_bits[svf_row] = bitarray(cls.word_size(svf_row))
svf_bits[svf_row].setall(1)
svf_bits[svf_row][svf_col] = jed_bit
return svf_bits
@staticmethod
def svf_to_jed_coords(svf_row, svf_col):
raise NotImplementedError
@classmethod
def svf_to_jed(cls, svf_bits):
jed_bits = bitarray(cls.fuse_count)
jed_bits.setall(0)
for svf_row, svf_word in svf_bits.items():
for svf_col, svf_bit in enumerate(svf_word):
jed_index = cls.svf_to_jed_coords(svf_row, svf_col)
if jed_index is None: continue
jed_bits[jed_index] = svf_bit
return jed_bits
class ATF1502ASDevice(ATF15xxDevice):
idcode = 0x0150203f
fuse_count = 16808
data_width = {
range( 0, 108): range(86),
range(128, 229): range(86),
(256,): range(32),
(512,): range(4),
(768,): range(16),
}
@staticmethod
def jed_to_svf_coords(jed_index):
if jed_index in range( 0, 7680):
return 12 + (jed_index - 0) % 96, 79 - (jed_index - 0) // 96
if jed_index in range( 7680, 15360):
return 128 + (jed_index - 7680) % 96, 79 - (jed_index - 7680) // 96
if jed_index in range(15360, 16320):
return 0 + (jed_index - 15360) // 80, 79 - (jed_index - 15360) % 80
if jed_index in range(16320, 16720):
return 224 + (jed_index - 16320) % 5, 79 - (jed_index - 16320) // 5
if jed_index in range(16720, 16750):
return 224 + (jed_index - 16320) % 5, 85 - (jed_index - 16320) // 5 + 80
if jed_index in range(16750, 16782):
return 256, 31 - (jed_index - 16750)
if jed_index in range(16782, 16786):
return 512, 3 - (jed_index - 16782)
if jed_index in range(16786, 16802):
return 768, 15 - (jed_index - 16786)
if jed_index in range(16802, 16808):
return # reserved
assert False
@staticmethod
def svf_to_jed_coords(svf_row, svf_col):
if svf_row in range( 0, 12):
if svf_col in range(0, 80):
return 15360 + (svf_row - 0) * 80 + (79 - svf_col)
else:
return # always 1
if svf_row in range( 12, 108):
if svf_col in range(0, 80):
return 0 + (svf_row - 12) + (79 - svf_col) * 96
else:
return # always 1
if svf_row in range(128, 224):
if svf_col in range(0, 80):
return 7680 + (svf_row - 128) + (79 - svf_col) * 96
else:
return # always 1
if svf_row in range(224, 229):
if svf_col in range(0, 80):
return 16320 + (svf_row - 224) + (79 - svf_col) * 5
else:
return 16720 + (svf_row - 224) + (85 - svf_col) * 5
if svf_row == 256:
return 16750 + (31 - svf_col)
if svf_row == 512:
return 16782 + ( 3 - svf_col)
if svf_row == 768:
return 16786 + (15 - svf_col)
assert False
class ATF1504ASDevice(ATF15xxDevice):
idcode = 0x0150403f
fuse_count = 34192
data_width = {
range( 0, 108): range(166),
range(128, 233): range(166),
(256,): range(32),
(512,): range(4),
(768,): range(16),
}
@staticmethod
def jed_to_svf_coords(jed_index):
if jed_index in range( 0, 15360):
return 12 + (jed_index - 0) % 96, 165 - (jed_index - 0) // 96
if jed_index in range(15360, 30720):
return 128 + (jed_index - 15360) % 96, 165 - (jed_index - 15360) // 96
if jed_index in range(30720, 32640):
return 0 + (jed_index - 30720) // 160, 165 - (jed_index - 30720) % 160
if jed_index in range(32640, 34134):
return 224 + (jed_index - 32640) % 9, 165 - (jed_index - 32640) // 9
if jed_index in range(34134, 34166):
return 256, 31 - (jed_index - 34134)
if jed_index in range(34166, 34170):
return 512, 3 - (jed_index - 34166)
if jed_index in range(34170, 34186):
return 768, 15 - (jed_index - 34170)
if jed_index in range(34186, 34192):
return # reserved
assert False
@staticmethod
def svf_to_jed_coords(svf_row, svf_col):
if svf_row in range( 0, 12):
if svf_col in range(6, 166):
return 30720 + (svf_row - 0) * 160 + (165 - svf_col)
else:
return # always 1
if svf_row in range( 12, 108):
if svf_col in range(6, 166):
return 0 + (svf_row - 12) + (165 - svf_col) * 96
else:
return # always 1
if svf_row in range(128, 224):
if svf_col in range(6, 166):
return 15360 + (svf_row - 128) + (165 - svf_col) * 96
else:
return # always 1
if svf_row in range(224, 233):
if svf_col in range(0, 166):
return 32640 + (svf_row - 224) + (165 - svf_col) * 9
else:
return # always 1
if svf_row == 256:
return 34134 + (31 - svf_col)
if svf_row == 512:
return 34166 + ( 3 - svf_col)
if svf_row == 768:
return 34170 + (15 - svf_col)
assert False
class ATF1508ASDevice(ATF15xxDevice):
idcode = 0x0150803f
fuse_count = 74136
data_width = {
range( 0, 108): range(326),
range(128, 251): range(326),
(256,): range(32),
(512,): range(4),
(768,): range(16),
}
@staticmethod
def jed_to_svf_coords(jed_index):
if jed_index in range( 0, 30720):
return 12 + (jed_index - 0) % 96, 325 - (jed_index - 0) // 96
if jed_index in range(30720, 61440):
return 128 + (jed_index - 30720) % 96, 325 - (jed_index - 30720) // 96
if jed_index in range(61440, 65280):
return 0 + (jed_index - 61440) // 320, 325 - (jed_index - 61440) % 320
if jed_index in range(65280, 74082):
return 224 + (jed_index - 65280) % 27, 325 - (jed_index - 65280) // 27
if jed_index in range(74082, 74114):
return 256, 31 - (jed_index - 74082)
if jed_index in range(74114, 74118):
return 512, 3 - (jed_index - 74114)
if jed_index in range(74118, 74134):
return 768, 15 - (jed_index - 74118)
if jed_index in range(74134, 74136):
return # reserved
assert False
@staticmethod
def svf_to_jed_coords(svf_row, svf_col):
if svf_row in range( 0, 12):
if svf_col in range(6, 326):
return 61440 + (svf_row - 0) * 320 + (325 - svf_col)
else:
return # always 1
if svf_row in range( 12, 108):
if svf_col in range(6, 326):
return 0 + (svf_row - 12) + (325 - svf_col) * 96
else:
return # always 1
if svf_row in range(128, 224):
if svf_col in range(6, 326):
return 30720 + (svf_row - 128) + (325 - svf_col) * 96
else:
return # always 1
if svf_row in range(224, 251):
if svf_col in range(0, 326):
return 65280 + (svf_row - 224) + (325 - svf_col) * 27
else:
return # always 1
if svf_row == 256:
return 74082 + (31 - svf_col)
if svf_row == 512:
return 74114 + ( 3 - svf_col)
if svf_row == 768:
return 74118 + (15 - svf_col)
assert False
if __name__ == '__main__':
with open('atf1502as_svf2jed.csv', 'w') as f:
f.write('SVF ROW,SVF COL,JED\n')
for svf_rows, svf_cols in ATF1502ASDevice.data_width.items():
for svf_row in svf_rows:
for svf_col in svf_cols:
jed_index = ATF1502ASDevice.svf_to_jed_coords(svf_row, svf_col)
if jed_index is None: jed_index = 0x7fff
f.write('{},{},{}\n'.format(svf_row, svf_col, jed_index))
with open('atf1502as_jed2svf.csv', 'w') as f:
f.write('JED,SVF ROW,SVF COL\n')
for jed_index in range(ATF1502ASDevice.fuse_count):
svf_index = ATF1502ASDevice.jed_to_svf_coords(jed_index)
if svf_index is None: continue
f.write('{},{},{}\n'.format(jed_index, *svf_index))
with open('atf1504as_svf2jed.csv', 'w') as f:
f.write('SVF ROW,SVF COL,JED\n')
for svf_rows, svf_cols in ATF1504ASDevice.data_width.items():
for svf_row in svf_rows:
for svf_col in svf_cols:
jed_index = ATF1504ASDevice.svf_to_jed_coords(svf_row, svf_col)
if jed_index is None: jed_index = 0xffff
f.write('{},{},{}\n'.format(svf_row, svf_col, jed_index))
with open('atf1504as_jed2svf.csv', 'w') as f:
f.write('JED,SVF ROW,SVF COL\n')
for jed_index in range(ATF1504ASDevice.fuse_count):
svf_index = ATF1504ASDevice.jed_to_svf_coords(jed_index)
if svf_index is None: continue
f.write('{},{},{}\n'.format(jed_index, *svf_index))
with open('atf1508as_svf2jed.csv', 'w') as f:
f.write('SVF ROW,SVF COL,JED\n')
for svf_rows, svf_cols in ATF1508ASDevice.data_width.items():
for svf_row in svf_rows:
for svf_col in svf_cols:
jed_index = ATF1508ASDevice.svf_to_jed_coords(svf_row, svf_col)
if jed_index is None: jed_index = 0x1ffff
f.write('{},{},{}\n'.format(svf_row, svf_col, jed_index))
with open('atf1508as_jed2svf.csv', 'w') as f:
f.write('JED,SVF ROW,SVF COL\n')
for jed_index in range(ATF1508ASDevice.fuse_count):
svf_index = ATF1508ASDevice.jed_to_svf_coords(jed_index)
if svf_index is None: continue
f.write('{},{},{}\n'.format(jed_index, *svf_index))

24
utils/jtag/example_jed2xsvf.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
set -e
# specify name of your PLD design and device to run on
APP=counter
DEV=ATF1502AS
PKG=PLCC44
SPD=15
## compile Verilog design by yosys
#../../../yosys/atf15xx_yosys/run_yosys.sh $APP > $APP.log
## use Atmel fitter to produce .jed file
#../../yosys/atf15xx_yosys/run_fitter.sh -d $DEV -p $PKG -s $SPD $APP -preassign keep -tdi_pullup on -tms_pullup on -output_fast off -xor_synthesis on $*
# convert jed to svf
python3 ./fuseconv.py -d $DEV $APP.jed $APP.svf
# convert svf to xsvf
python3 .//svf2xsvf.py $APP.svf $APP.xsvf
date
echo "done!"

223
utils/jtag/fuseconv.py Normal file
View File

@ -0,0 +1,223 @@
import argparse
import textwrap
from bitarray import bitarray
from jesd3 import JESD3Parser
from svf import SVFParser, SVFEventHandler
from device import *
def read_jed(file):
parser = JESD3Parser(file.read())
parser.parse()
return parser.fuse, parser.design_spec
def write_jed(file, jed_bits, *, comment):
assert '*' not in comment
file.write("\x02{}*\n".format(comment))
file.write("QF{}* F0*\n".format(len(jed_bits)))
chunk_size = 64
for start in range(0, len(jed_bits), chunk_size):
file.write("L{:05d} {}*\n".format(start, jed_bits[start:start+chunk_size].to01()))
file.write("\x030000\n")
class ATFSVFEventHandler(SVFEventHandler):
def ignored(self, *args, **kwargs):
pass
svf_frequency = ignored
svf_trst = ignored
svf_state = ignored
svf_endir = ignored
svf_enddr = ignored
svf_hir = ignored
svf_sir = ignored
svf_tir = ignored
svf_hdr = ignored
svf_sdr = ignored
svf_tdr = ignored
svf_runtest = ignored
svf_piomap = ignored
svf_pio = ignored
def __init__(self):
self.ir = None
self.erase = False
self.addr = 0
self.data = b''
self.bits = {}
def svf_sir(self, tdi, smask, tdo, mask):
self.ir = int.from_bytes(tdi.tobytes(), 'little')
if self.ir == ATF15xxInstr.ISC_LATCH_ERASE:
self.erase = True
if self.ir == ATF15xxInstr.ISC_DATA:
self.erase = False
def svf_sdr(self, tdi, smask, tdo, mask):
if self.ir == ATF15xxInstr.ISC_ADDRESS:
self.addr = int.from_bytes(tdi.tobytes(), 'little')
if (self.ir & ~0x3) == ATF15xxInstr.ISC_DATA:
self.data = tdi
def svf_runtest(self, run_state, run_count, run_clock, min_time, max_time, end_state):
if not self.erase and self.ir == ATF15xxInstr.ISC_PROGRAM_ERASE:
self.bits[self.addr] = self.data
def read_svf(file):
handler = ATFSVFEventHandler()
parser = SVFParser(file.read(), handler)
parser.parse_file()
return handler.bits, ''
def _bitarray_to_hex(input_bits):
bits = bitarray(input_bits, endian="little")
bits.bytereverse()
bits.reverse()
return bits.tobytes().hex()
def write_svf(file, svf_bits, device, *, comment):
# This code is kind of awful.
def emit_header():
for comment_line in comment.splitlines():
file.write("// {}\n".format(comment_line))
file.write("TRST ABSENT;\n")
file.write("ENDIR IDLE;\n")
file.write("ENDDR IDLE;\n")
file.write("HDR 0;\n")
file.write("HIR 0;\n")
file.write("TDR 0;\n")
file.write("TIR 0;\n")
file.write("STATE RESET;\n")
def emit_check_idcode(idcode):
file.write("// Check IDCODE\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.IDCODE))
file.write("SDR 32 TDI (ffffffff)\n\tTDO ({:08x})\n\tMASK (ffffffff);\n".format(idcode))
def emit_enable():
file.write("// ISC enable\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_CONFIG))
file.write("SDR 10 TDI ({:03x});\n".format(0x1b9)) # magic constant?
file.write("STATE IDLE;\n")
def emit_disable():
file.write("// ISC disable\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_CONFIG))
file.write("SDR 10 TDI ({:03x});\n".format(0x000))
file.write("STATE IDLE;\n")
def emit_unknown():
# ATMISP does this for unknown reasons. DR seems to be just BYPASS. Removing this
# doesn't do anything (and shouldn't do anything, since ATMISP doesn't go through RTI
# or capture/update DR), but let's keep it for now. Vendor tools wouldn't emit SIR
# without any reason whatsoever, right? Right??
file.write("// ISC unknown\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_UNKNOWN))
def emit_erase():
file.write("// ISC erase\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_LATCH_ERASE))
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_PROGRAM_ERASE))
file.write("RUNTEST IDLE 210E-3 SEC;\n")
emit_unknown()
def emit_program(address, data):
file.write("// ISC program word\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_ADDRESS))
file.write("SDR 11 TDI ({:03x});\n".format(address))
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_DATA | (address >> 8)))
file.write("SDR {} TDI ({:0{}x});\n".format(len(data),
int(data.to01()[::-1], 2), len(data) // 4))
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_PROGRAM_ERASE))
file.write("RUNTEST IDLE 30E-3 SEC;\n")
emit_unknown()
def emit_verify(address, data):
file.write("// ISC verify word\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_ADDRESS))
file.write("SDR 11 TDI ({:03x});\n".format(address))
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_READ))
file.write("RUNTEST IDLE 20E-3 SEC;\n")
file.write("SIR 10 TDI ({:03x});\n".format(ATF15xxInstr.ISC_DATA | (address >> 8)))
file.write("SDR {} TDI ({:0{}x})\n\tTDO ({:0{}x})\n\tMASK ({:0{}x});\n".format(len(data),
int(data.to01()[::-1], 2), len(data) // 4,
int(data.to01()[::-1], 2), len(data) // 4,
(1 << len(data)) - 1, len(data) // 4))
emit_header()
emit_check_idcode(device.idcode)
emit_enable()
emit_erase()
for svf_row in svf_bits:
emit_program(svf_row, svf_bits[svf_row])
for svf_row in svf_bits:
emit_verify(svf_row, svf_bits[svf_row])
emit_disable()
class ATFFileType(argparse.FileType):
def __call__(self, value):
file = super().__call__(value)
filename = file.name.lower()
if not (filename.endswith('.jed') or filename.endswith('.svf')):
raise argparse.ArgumentTypeError('{} is not a JED or SVF file'.format(filename))
return file
def arg_parser():
parser = argparse.ArgumentParser(description=textwrap.dedent("""
Convert between ATF15xx JED and SVF files. The type of the file is determined by the extension
(``.jed`` or ``.svf``, respectively).
If an SVF file is provided as an input, it is used to drive the JTAG programming state machine
of a simulated device. The state machine is greatly simplified and only functions correctly
when driven with vectors that program every non-reserved bit at least once.
"""))
parser.add_argument(
'-d', '--device', metavar='DEVICE',
choices=('ATF1502AS', 'ATF1504AS', 'ATF1508AS'), default='ATF1502AS',
help='Select the device to use.')
parser.add_argument(
'input', metavar='INPUT', type=ATFFileType('r'),
help='Read fuses from file INPUT.')
parser.add_argument(
'output', metavar='OUTPUT', type=ATFFileType('w'),
help='Write fuses to file OUTPUT.')
return parser
def main():
args = arg_parser().parse_args()
if args.device == 'ATF1502AS':
device = ATF1502ASDevice
elif args.device == 'ATF1504AS':
device = ATF1504ASDevice
elif args.device == 'ATF1508AS':
device = ATF1508ASDevice
else:
assert False
jed_bits = svf_bits = None
if args.input.name.lower().endswith('.jed'):
jed_bits, comment = read_jed(args.input)
if device.fuse_count != len(jed_bits):
raise SystemExit(f"Device has {device.fuse_count} fuses, JED file "
f"has {len(jed_bits)}; wrong --device option?")
elif args.input.name.lower().endswith('.svf'):
svf_bits, comment = read_svf(args.input)
else:
assert False
if args.output.name.lower().endswith('.jed'):
if jed_bits is None:
jed_bits = device.svf_to_jed(svf_bits)
write_jed(args.output, jed_bits, comment=comment)
elif args.output.name.lower().endswith('.svf'):
if svf_bits is None:
svf_bits = device.jed_to_svf(jed_bits)
write_svf(args.output, svf_bits, device, comment=comment)
else:
assert False
if __name__ == '__main__':
main()

300
utils/jtag/jesd3.py Normal file
View File

@ -0,0 +1,300 @@
# 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()))

22
utils/jtag/readme.txt Normal file
View File

@ -0,0 +1,22 @@
JTAG conversion tools
---------------------------------------
fuseconv.py : converts ATF150X .jed file into .svf file
The origin of the conversion tool is this git repo:
https://github.com/whitequark/prjbureau
svf2xsvf.py : converts .svf file into .xsvf file
The origin of the tool is this git repo:
https://github.com/arduino/OpenOCD/tree/master/contrib/xsvf_tools
Typically you produce a .jed file for your ATF150X device either by
WinCUPL or by ATF15XX_Yosys (https://github.com/hoglet67/atf15xx_yosys/)
and then run fuseconv.py and svf2xsvf.py to produce .xsvf file that can be
used by Aftereburner's JTAG player.
See example_jed2xsvf.sh for more information how to run these tools.

906
utils/jtag/svf.py Normal file
View File

@ -0,0 +1,906 @@
# 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()

729
utils/jtag/svf2xsvf.py Normal file
View File

@ -0,0 +1,729 @@
#!/usr/bin/python3.0
# Copyright 2008, SoftPLC Corporation http://softplc.com
# Dick Hollenbeck dick@softplc.com
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you may find one here:
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# or you may search the http://www.gnu.org website for the version 2 license,
# or you may write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
# A python program to convert an SVF file to an XSVF file. There is an
# option to include comments containing the source file line number from the origin
# SVF file before each outputted XSVF statement.
#
# We deviate from the XSVF spec in that we introduce a new command called
# XWAITSTATE which directly flows from the SVF RUNTEST command. Unfortunately
# XRUNSTATE was ill conceived and is not used here. We also add support for the
# three Lattice extensions to SVF: LCOUNT, LDELAY, and LSDR. The xsvf file
# generated from this program is suitable for use with the xsvf player in
# OpenOCD with my modifications to xsvf.c.
#
# This program is written for python 3.0, and it is not easy to change this
# back to 2.x. You may find it easier to use python 3.x even if that means
# building it.
import re
import sys
import struct
# There are both ---<Lexer>--- and ---<Parser>--- sections to this program
if len( sys.argv ) < 3:
print("usage %s <svf_filename> <xsvf_filename>" % sys.argv[0])
exit(1)
inputFilename = sys.argv[1]
outputFilename = sys.argv[2]
#doCOMMENTs = True # Save XCOMMENTs in the output xsvf file
doCOMMENTs = False # Save XCOMMENTs in the output xsvf file
# pick your file encoding
file_encoding = 'ISO-8859-1'
#file_encoding = 'utf-8'
xrepeat = 0 # argument to XREPEAT, gives retry count for masked compares
#-----< Lexer >---------------------------------------------------------------
StateBin = (RESET,IDLE,
DRSELECT,DRCAPTURE,DRSHIFT,DREXIT1,DRPAUSE,DREXIT2,DRUPDATE,
IRSELECT,IRCAPTURE,IRSHIFT,IREXIT1,IRPAUSE,IREXIT2,IRUPDATE) = range(16)
# Any integer index into this tuple will be equal to its corresponding StateBin value
StateTxt = ("RESET","IDLE",
"DRSELECT","DRCAPTURE","DRSHIFT","DREXIT1","DRPAUSE","DREXIT2","DRUPDATE",
"IRSELECT","IRCAPTURE","IRSHIFT","IREXIT1","IRPAUSE","IREXIT2","IRUPDATE")
(XCOMPLETE,XTDOMASK,XSIR,XSDR,XRUNTEST,hole0,hole1,XREPEAT,XSDRSIZE,XSDRTDO,
XSETSDRMASKS,XSDRINC,XSDRB,XSDRC,XSDRE,XSDRTDOB,XSDRTDOC,
XSDRTDOE,XSTATE,XENDIR,XENDDR,XSIR2,XCOMMENT,XWAIT,XWAITSTATE,
LCOUNT,LDELAY,LSDR,XTRST) = range(29)
#Note: LCOUNT, LDELAY, and LSDR are Lattice extensions to SVF and provide a way to loop back
# and check a completion status, essentially waiting on a part until it signals that it is done.
# For example below: loop 25 times, each time through the loop do a LDELAY (same as a true RUNTEST)
# and exit loop when LSDR compares match.
"""
LCOUNT 25;
! Step to DRPAUSE give 5 clocks and wait for 1.00e+000 SEC.
LDELAY DRPAUSE 5 TCK 1.00E-003 SEC;
! Test for the completed status. Match means pass.
! Loop back to LDELAY line if not match and loop count less than 25.
LSDR 1 TDI (0)
TDO (1);
"""
#XTRST is an opcode Xilinx seemed to have missed and it comes from the SVF TRST statement.
LineNumber = 1
def s_ident(scanner, token): return ("ident", token.upper(), LineNumber)
def s_hex(scanner, token):
global LineNumber
LineNumber = LineNumber + token.count('\n')
token = ''.join(token.split())
return ("hex", token[1:-1], LineNumber)
def s_int(scanner, token): return ("int", int(token), LineNumber)
def s_float(scanner, token): return ("float", float(token), LineNumber)
#def s_comment(scanner, token): return ("comment", token, LineNumber)
def s_semicolon(scanner, token): return ("semi", token, LineNumber)
def s_nl(scanner,token):
global LineNumber
LineNumber = LineNumber + 1
#print( 'LineNumber=', LineNumber, file=sys.stderr )
return None
#2.00E-002
scanner = re.Scanner([
(r"[a-zA-Z]\w*", s_ident),
# (r"[-+]?[0-9]+[.]?[0-9]*([eE][-+]?[0-9]+)?", s_float),
(r"[-+]?[0-9]+(([.][0-9eE+-]*)|([eE]+[-+]?[0-9]+))", s_float),
(r"\d+", s_int),
(r"\(([0-9a-fA-F]|\s)*\)", s_hex),
(r"(!|//).*$", None),
(r";", s_semicolon),
(r"\n",s_nl),
(r"\s*", None),
],
re.MULTILINE
)
# open the file using the given encoding
file = open( sys.argv[1], encoding=file_encoding )
# read all svf file input into string "input"
input = file.read()
file.close()
# Lexer:
# create a list of tuples containing (tokenType, tokenValue, LineNumber)
tokens = scanner.scan( input )[0]
input = None # allow gc to reclaim memory holding file
#for tokenType, tokenValue, ln in tokens: print( "line %d: %s" % (ln, tokenType), tokenValue )
#-----<parser>-----------------------------------------------------------------
tokVal = tokType = tokLn = None
tup = iter( tokens )
def nextTok():
"""
Function to read the next token from tup into tokType, tokVal, tokLn (linenumber)
which are globals.
"""
global tokType, tokVal, tokLn, tup
tokType, tokVal, tokLn = tup.__next__()
class ParseError(Exception):
"""A class to hold a parsing error message"""
def __init__(self, linenumber, token, message):
self.linenumber = linenumber
self.token = token
self.message = message
def __str__(self):
global inputFilename
return "Error in file \'%s\' at line %d near token %s\n %s" % (
inputFilename, self.linenumber, repr(self.token), self.message)
class MASKSET(object):
"""
Class MASKSET holds a set of bit vectors, all of which are related, will all
have the same length, and are associated with one of the seven shiftOps:
HIR, HDR, TIR, TDR, SIR, SDR, LSDR. One of these holds a mask, smask, tdi, tdo, and a
size.
"""
def __init__(self, name):
self.empty()
self.name = name
def empty(self):
self.mask = bytearray()
self.smask = bytearray()
self.tdi = bytearray()
self.tdo = bytearray()
self.size = 0
def syncLengths( self, sawTDI, sawTDO, sawMASK, sawSMASK, newSize ):
"""
Set all the lengths equal in the event some of the masks were
not seen as part of the last change set.
"""
if self.size == newSize:
return
if newSize == 0:
self.empty()
return
# If an SIR was given without a MASK(), then use a mask of all zeros.
# this is not consistent with the SVF spec, but it makes sense because
# it would be odd to be testing an instruction register read out of a
# tap without giving a mask for it. Also, lattice seems to agree and is
# generating SVF files that comply with this philosophy.
if self.name == 'SIR' and not sawMASK:
self.mask = bytearray( newSize )
if newSize != len(self.mask):
self.mask = bytearray( newSize )
if self.name == 'SDR': # leave mask for HIR,HDR,TIR,TDR,SIR zeros
for i in range( newSize ):
self.mask[i] = 1
if newSize != len(self.tdo):
self.tdo = bytearray( newSize )
if newSize != len(self.tdi):
self.tdi = bytearray( newSize )
if newSize != len(self.smask):
self.smask = bytearray( newSize )
self.size = newSize
#-----</MASKSET>-----
def makeBitArray( hexString, bitCount ):
"""
Converts a packed sequence of hex ascii characters into a bytearray where
each element in the array holds exactly one bit. Only "bitCount" bits are
scanned and these must be the least significant bits in the hex number. That
is, it is legal to have some unused bits in the must significant hex nibble
of the input "hexString". The string is scanned starting from the backend,
then just before returning we reverse the array. This way the append()
method can be used, which I assume is faster than an insert.
"""
global tokLn
a = bytearray()
length = bitCount
hexString = list(hexString)
hexString.reverse()
#print(hexString)
for c in hexString:
if length <= 0:
break;
c = int(c, 16)
for mask in [1,2,4,8]:
if length <= 0:
break;
length = length - 1
a.append( (c & mask) != 0 )
if length > 0:
raise ParseError( tokLn, hexString, "Insufficient hex characters for given length of %d" % bitCount )
a.reverse()
#print(a)
return a
def makeXSVFbytes( bitarray ):
"""
Make a bytearray which is contains the XSVF bits which will be written
directly to disk. The number of bytes needed is calculated from the size
of the argument bitarray.
"""
bitCount = len(bitarray)
byteCount = (bitCount+7)//8
ba = bytearray( byteCount )
firstBit = (bitCount % 8) - 1
if firstBit == -1:
firstBit = 7
bitNdx = 0
for byteNdx in range(byteCount):
mask = 1<<firstBit
byte = 0
while mask:
if bitarray[bitNdx]:
byte |= mask;
mask = mask >> 1
bitNdx = bitNdx + 1
ba[byteNdx] = byte
firstBit = 7
return ba
def writeComment( outputFile, shiftOp_linenum, shiftOp ):
"""
Write an XCOMMENT record to outputFile
"""
comment = "%s @%d\0" % (shiftOp, shiftOp_linenum) # \0 is terminating nul
ba = bytearray(1)
ba[0] = XCOMMENT
ba += comment.encode()
outputFile.write( ba )
def combineBitVectors( trailer, meat, header ):
"""
Combine the 3 bit vectors comprizing a transmission. Since the least
significant bits are sent first, the header is put onto the list last so
they are sent first from that least significant position.
"""
ret = bytearray()
ret.extend( trailer )
ret.extend( meat )
ret.extend( header )
return ret
def writeRUNTEST( outputFile, run_state, end_state, run_count, min_time, tokenTxt ):
"""
Write the output for the SVF RUNTEST command.
run_count - the number of clocks
min_time - the number of seconds
tokenTxt - either RUNTEST or LDELAY
"""
# convert from secs to usecs
min_time = int( min_time * 1000000)
# the SVF RUNTEST command does NOT map to the XSVF XRUNTEST command. Check the SVF spec, then
# read the XSVF command. They are not the same. Use an XSVF XWAITSTATE to
# implement the required behavior of the SVF RUNTEST command.
if doCOMMENTs:
writeComment( output, tokLn, tokenTxt )
if tokenTxt == 'RUNTEST':
obuf = bytearray(11)
obuf[0] = XWAITSTATE
obuf[1] = run_state
obuf[2] = end_state
struct.pack_into(">i", obuf, 3, run_count ) # big endian 4 byte int to obuf
struct.pack_into(">i", obuf, 7, min_time ) # big endian 4 byte int to obuf
outputFile.write( obuf )
else: # == 'LDELAY'
obuf = bytearray(10)
obuf[0] = LDELAY
obuf[1] = run_state
# LDELAY has no end_state
struct.pack_into(">i", obuf, 2, run_count ) # big endian 4 byte int to obuf
struct.pack_into(">i", obuf, 6, min_time ) # big endian 4 byte int to obuf
outputFile.write( obuf )
output = open( outputFilename, mode='wb' )
hir = MASKSET('HIR')
hdr = MASKSET('HDR')
tir = MASKSET('TIR')
tdr = MASKSET('TDR')
sir = MASKSET('SIR')
sdr = MASKSET('SDR')
expecting_eof = True
# one of the commands that take the shiftParts after the length, the parse
# template for all of these commands is identical
shiftOps = ('SDR', 'SIR', 'LSDR', 'HDR', 'HIR', 'TDR', 'TIR')
# the order must correspond to shiftOps, this holds the MASKSETS. 'LSDR' shares sdr with 'SDR'
shiftSets = (sdr, sir, sdr, hdr, hir, tdr, tir )
# what to expect as parameters to a shiftOp, i.e. after a SDR length or SIR length
shiftParts = ('TDI', 'TDO', 'MASK', 'SMASK')
# the set of legal states which can trail the RUNTEST command
run_state_allowed = ('IRPAUSE', 'DRPAUSE', 'RESET', 'IDLE')
enddr_state_allowed = ('DRPAUSE', 'IDLE')
endir_state_allowed = ('IRPAUSE', 'IDLE')
trst_mode_allowed = ('ON', 'OFF', 'Z', 'ABSENT')
enddr_state = IDLE
endir_state = IDLE
frequency = 1.00e+006 # HZ;
# change detection for xsdrsize and xtdomask
xsdrsize = -1 # the last one sent, send only on change
xtdomask = bytearray() # the last one sent, send only on change
# we use a number of single byte writes for the XSVF command below
cmdbuf = bytearray(1)
# Save the XREPEAT setting into the file as first thing.
obuf = bytearray(2)
obuf[0] = XREPEAT
obuf[1] = xrepeat
output.write( obuf )
try:
while 1:
expecting_eof = True
nextTok()
expecting_eof = False
# print( tokType, tokVal, tokLn )
if tokVal in shiftOps:
shiftOp_linenum = tokLn
shiftOp = tokVal
set = shiftSets[shiftOps.index(shiftOp)]
# set flags false, if we see one later, set that one true later
sawTDI = sawTDO = sawMASK = sawSMASK = False
nextTok()
if tokType != 'int':
raise ParseError( tokLn, tokVal, "Expecting 'int' giving %s length, got '%s'" % (shiftOp, tokType) )
length = tokVal
nextTok()
while tokVal != ';':
if tokVal not in shiftParts:
raise ParseError( tokLn, tokVal, "Expecting TDI, TDO, MASK, SMASK, or ';'")
shiftPart = tokVal
nextTok()
if tokType != 'hex':
raise ParseError( tokLn, tokVal, "Expecting hex bits" )
bits = makeBitArray( tokVal, length )
if shiftPart == 'TDI':
sawTDI = True
set.tdi = bits
elif shiftPart == 'TDO':
sawTDO = True
set.tdo = bits
elif shiftPart == 'MASK':
sawMASK = True
set.mask = bits
elif shiftPart == 'SMASK':
sawSMASK = True
set.smask = bits
nextTok()
set.syncLengths( sawTDI, sawTDO, sawMASK, sawSMASK, length )
# process all the gathered parameters and generate outputs here
if shiftOp == 'SIR':
if doCOMMENTs:
writeComment( output, shiftOp_linenum, 'SIR' )
tdi = combineBitVectors( tir.tdi, sir.tdi, hir.tdi )
if len(tdi) > 255:
obuf = bytearray(3)
obuf[0] = XSIR2
struct.pack_into( ">h", obuf, 1, len(tdi) )
else:
obuf = bytearray(2)
obuf[0] = XSIR
obuf[1] = len(tdi)
output.write( obuf )
obuf = makeXSVFbytes( tdi )
output.write( obuf )
elif shiftOp == 'SDR':
if doCOMMENTs:
writeComment( output, shiftOp_linenum, shiftOp )
if not sawTDO:
# pass a zero filled bit vector for the sdr.mask
mask = combineBitVectors( tdr.mask, bytearray(sdr.size), hdr.mask )
tdi = combineBitVectors( tdr.tdi, sdr.tdi, hdr.tdi )
if xsdrsize != len(tdi):
xsdrsize = len(tdi)
cmdbuf[0] = XSDRSIZE
output.write( cmdbuf )
obuf = bytearray(4)
struct.pack_into( ">i", obuf, 0, xsdrsize ) # big endian 4 byte int to obuf
output.write( obuf )
if xtdomask != mask:
xtdomask = mask
cmdbuf[0] = XTDOMASK
output.write( cmdbuf )
obuf = makeXSVFbytes( mask )
output.write( obuf )
cmdbuf[0] = XSDR
output.write( cmdbuf )
obuf = makeXSVFbytes( tdi )
output.write( obuf )
else:
mask = combineBitVectors( tdr.mask, sdr.mask, hdr.mask )
tdi = combineBitVectors( tdr.tdi, sdr.tdi, hdr.tdi )
tdo = combineBitVectors( tdr.tdo, sdr.tdo, hdr.tdo )
if xsdrsize != len(tdi):
xsdrsize = len(tdi)
cmdbuf[0] = XSDRSIZE
output.write( cmdbuf )
obuf = bytearray(4)
struct.pack_into(">i", obuf, 0, xsdrsize ) # big endian 4 byte int to obuf
output.write( obuf )
if xtdomask != mask:
xtdomask = mask
cmdbuf[0] = XTDOMASK
output.write( cmdbuf )
obuf = makeXSVFbytes( mask )
output.write( obuf )
cmdbuf[0] = XSDRTDO
output.write( cmdbuf )
obuf = makeXSVFbytes( tdi )
output.write( obuf )
obuf = makeXSVFbytes( tdo )
output.write( obuf )
#print( "len(tdo)=", len(tdo), "len(tdr.tdo)=", len(tdr.tdo), "len(sdr.tdo)=", len(sdr.tdo), "len(hdr.tdo)=", len(hdr.tdo) )
elif shiftOp == 'LSDR':
if doCOMMENTs:
writeComment( output, shiftOp_linenum, shiftOp )
mask = combineBitVectors( tdr.mask, sdr.mask, hdr.mask )
tdi = combineBitVectors( tdr.tdi, sdr.tdi, hdr.tdi )
tdo = combineBitVectors( tdr.tdo, sdr.tdo, hdr.tdo )
if xsdrsize != len(tdi):
xsdrsize = len(tdi)
cmdbuf[0] = XSDRSIZE
output.write( cmdbuf )
obuf = bytearray(4)
struct.pack_into(">i", obuf, 0, xsdrsize ) # big endian 4 byte int to obuf
output.write( obuf )
if xtdomask != mask:
xtdomask = mask
cmdbuf[0] = XTDOMASK
output.write( cmdbuf )
obuf = makeXSVFbytes( mask )
output.write( obuf )
cmdbuf[0] = LSDR
output.write( cmdbuf )
obuf = makeXSVFbytes( tdi )
output.write( obuf )
obuf = makeXSVFbytes( tdo )
output.write( obuf )
#print( "len(tdo)=", len(tdo), "len(tdr.tdo)=", len(tdr.tdo), "len(sdr.tdo)=", len(sdr.tdo), "len(hdr.tdo)=", len(hdr.tdo) )
elif tokVal == 'RUNTEST' or tokVal == 'LDELAY':
# e.g. from lattice tools:
# "RUNTEST IDLE 5 TCK 1.00E-003 SEC;"
saveTok = tokVal
nextTok()
min_time = 0
run_count = 0
max_time = 600 # ten minutes
if tokVal in run_state_allowed:
run_state = StateTxt.index(tokVal)
end_state = run_state # bottom of page 17 of SVF spec
nextTok()
if tokType != 'int' and tokType != 'float':
raise ParseError( tokLn, tokVal, "Expecting 'int' or 'float' after RUNTEST [run_state]")
timeval = tokVal;
nextTok()
if tokVal != 'TCK' and tokVal != 'SEC' and tokVal != 'SCK':
raise ParseError( tokLn, tokVal, "Expecting 'TCK' or 'SEC' or 'SCK' after RUNTEST [run_state] (run_count|min_time)")
if tokVal == 'TCK' or tokVal == 'SCK':
run_count = int( timeval )
else:
min_time = timeval
nextTok()
if tokType == 'int' or tokType == 'float':
min_time = tokVal
nextTok()
if tokVal != 'SEC':
raise ParseError( tokLn, tokVal, "Expecting 'SEC' after RUNTEST [run_state] run_count min_time")
nextTok()
if tokVal == 'MAXIMUM':
nextTok()
if tokType != 'int' and tokType != 'float':
raise ParseError( tokLn, tokVal, "Expecting 'max_time' after RUNTEST [run_state] min_time SEC MAXIMUM")
max_time = tokVal
nextTok()
if tokVal != 'SEC':
raise ParseError( tokLn, tokVal, "Expecting 'max_time' after RUNTEST [run_state] min_time SEC MAXIMUM max_time")
nextTok()
if tokVal == 'ENDSTATE':
nextTok()
if tokVal not in run_state_allowed:
raise ParseError( tokLn, tokVal, "Expecting 'run_state' after RUNTEST .... ENDSTATE")
end_state = StateTxt.index(tokVal)
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after RUNTEST ....")
# print( "run_count=", run_count, "min_time=", min_time,
# "max_time=", max_time, "run_state=", State[run_state], "end_state=", State[end_state] )
writeRUNTEST( output, run_state, end_state, run_count, min_time, saveTok )
elif tokVal == 'LCOUNT':
nextTok()
if tokType != 'int':
raise ParseError( tokLn, tokVal, "Expecting integer 'count' after LCOUNT")
loopCount = tokVal
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after LCOUNT count")
if doCOMMENTs:
writeComment( output, tokLn, 'LCOUNT' )
obuf = bytearray(5)
obuf[0] = LCOUNT
struct.pack_into(">i", obuf, 1, loopCount ) # big endian 4 byte int to obuf
output.write( obuf )
elif tokVal == 'ENDDR':
nextTok()
if tokVal not in enddr_state_allowed:
raise ParseError( tokLn, tokVal, "Expecting 'stable_state' after ENDDR. (one of: DRPAUSE, IDLE)")
enddr_state = StateTxt.index(tokVal)
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after ENDDR stable_state")
if doCOMMENTs:
writeComment( output, tokLn, 'ENDDR' )
obuf = bytearray(2)
obuf[0] = XENDDR
# Page 10 of the March 1999 SVF spec shows that RESET is also allowed here.
# Yet the XSVF spec has no provision for that, and uses a non-standard, i.e.
# boolean argument to XENDDR which only handles two of the 3 intended states.
obuf[1] = 1 if enddr_state == DRPAUSE else 0
output.write( obuf )
elif tokVal == 'ENDIR':
nextTok()
if tokVal not in endir_state_allowed:
raise ParseError( tokLn, tokVal, "Expecting 'stable_state' after ENDIR. (one of: IRPAUSE, IDLE)")
endir_state = StateTxt.index(tokVal)
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after ENDIR stable_state")
if doCOMMENTs:
writeComment( output, tokLn, 'ENDIR' )
obuf = bytearray(2)
obuf[0] = XENDIR
# Page 10 of the March 1999 SVF spec shows that RESET is also allowed here.
# Yet the XSVF spec has no provision for that, and uses a non-standard, i.e.
# boolean argument to XENDDR which only handles two of the 3 intended states.
obuf[1] = 1 if endir_state == IRPAUSE else 0
output.write( obuf )
elif tokVal == 'STATE':
nextTok()
ln = tokLn
while tokVal != ';':
if tokVal not in StateTxt:
raise ParseError( tokLn, tokVal, "Expecting 'stable_state' after STATE")
stable_state = StateTxt.index( tokVal )
if doCOMMENTs and ln != -1:
writeComment( output, ln, 'STATE' )
ln = -1 # save comment only once
obuf = bytearray(2)
obuf[0] = XSTATE
obuf[1] = stable_state
output.write( obuf )
nextTok()
elif tokVal == 'FREQUENCY':
nextTok()
if tokVal != ';':
if tokType != 'int' and tokType != 'float':
raise ParseError( tokLn, tokVal, "Expecting 'cycles HZ' after FREQUENCY")
frequency = tokVal
nextTok()
if tokVal != 'HZ':
raise ParseError( tokLn, tokVal, "Expecting 'HZ' after FREQUENCY cycles")
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after FREQUENCY cycles HZ")
elif tokVal == 'TRST':
nextTok()
if tokVal not in trst_mode_allowed:
raise ParseError( tokLn, tokVal, "Expecting 'ON|OFF|Z|ABSENT' after TRST")
trst_mode = tokVal
nextTok()
if tokVal != ';':
raise ParseError( tokLn, tokVal, "Expecting ';' after TRST trst_mode")
if doCOMMENTs:
writeComment( output, tokLn, 'TRST %s' % trst_mode )
obuf = bytearray( 2 )
obuf[0] = XTRST
obuf[1] = trst_mode_allowed.index( trst_mode ) # use the index as the binary argument to XTRST opcode
output.write( obuf )
else:
raise ParseError( tokLn, tokVal, "Unknown token '%s'" % tokVal)
except StopIteration:
if not expecting_eof:
print( "Unexpected End of File at line ", tokLn )
except ParseError as pe:
print( "\n", pe )
finally:
# print( "closing file" )
cmdbuf[0] = XCOMPLETE
output.write( cmdbuf )
output.close()