mirror of
https://github.com/ole00/afterburner.git
synced 2024-11-21 15:30:52 +00:00
added JTAG utils for .jed to .xsvf conversion
This commit is contained in:
parent
7d20d778c6
commit
18b3cea6a2
310
utils/jtag/device.py
Normal file
310
utils/jtag/device.py
Normal 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
24
utils/jtag/example_jed2xsvf.sh
Executable 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
223
utils/jtag/fuseconv.py
Normal 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
300
utils/jtag/jesd3.py
Normal 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
22
utils/jtag/readme.txt
Normal 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
906
utils/jtag/svf.py
Normal 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
729
utils/jtag/svf2xsvf.py
Normal 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()
|
||||
|
Loading…
Reference in New Issue
Block a user