From 18b3cea6a2779db6f17f5ea052b67265d6b89244 Mon Sep 17 00:00:00 2001 From: ole00 Date: Fri, 12 Apr 2024 23:05:03 +0100 Subject: [PATCH] added JTAG utils for .jed to .xsvf conversion --- utils/jtag/device.py | 310 +++++++++++ utils/jtag/example_jed2xsvf.sh | 24 + utils/jtag/fuseconv.py | 223 ++++++++ utils/jtag/jesd3.py | 300 +++++++++++ utils/jtag/readme.txt | 22 + utils/jtag/svf.py | 906 +++++++++++++++++++++++++++++++++ utils/jtag/svf2xsvf.py | 729 ++++++++++++++++++++++++++ 7 files changed, 2514 insertions(+) create mode 100644 utils/jtag/device.py create mode 100755 utils/jtag/example_jed2xsvf.sh create mode 100644 utils/jtag/fuseconv.py create mode 100644 utils/jtag/jesd3.py create mode 100644 utils/jtag/readme.txt create mode 100644 utils/jtag/svf.py create mode 100644 utils/jtag/svf2xsvf.py diff --git a/utils/jtag/device.py b/utils/jtag/device.py new file mode 100644 index 0000000..f752ce7 --- /dev/null +++ b/utils/jtag/device.py @@ -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)) diff --git a/utils/jtag/example_jed2xsvf.sh b/utils/jtag/example_jed2xsvf.sh new file mode 100755 index 0000000..de449fe --- /dev/null +++ b/utils/jtag/example_jed2xsvf.sh @@ -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!" \ No newline at end of file diff --git a/utils/jtag/fuseconv.py b/utils/jtag/fuseconv.py new file mode 100644 index 0000000..5e1ffd3 --- /dev/null +++ b/utils/jtag/fuseconv.py @@ -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() diff --git a/utils/jtag/jesd3.py b/utils/jtag/jesd3.py new file mode 100644 index 0000000..6f7956f --- /dev/null +++ b/utils/jtag/jesd3.py @@ -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())) diff --git a/utils/jtag/readme.txt b/utils/jtag/readme.txt new file mode 100644 index 0000000..1677275 --- /dev/null +++ b/utils/jtag/readme.txt @@ -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. + diff --git a/utils/jtag/svf.py b/utils/jtag/svf.py new file mode 100644 index 0000000..9c1f4c7 --- /dev/null +++ b/utils/jtag/svf.py @@ -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() diff --git a/utils/jtag/svf2xsvf.py b/utils/jtag/svf2xsvf.py new file mode 100644 index 0000000..06c4e33 --- /dev/null +++ b/utils/jtag/svf2xsvf.py @@ -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 ------ and ------ sections to this program + + +if len( sys.argv ) < 3: + print("usage %s " % 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 ) + + +#---------------------------------------------------------------------- + +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 +#---------- + + +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<> 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() +