mirror of https://github.com/openXC7/prjxray.git
425 lines
14 KiB
Python
Executable File
425 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
'''
|
|
Spartan 6 bitstream analyzer tool.
|
|
|
|
This script reads a Spartan6 bitstream and prints out some useful information.
|
|
It can also create a frames file with the configuration data words.
|
|
The bitstream is analyzed word by word and interpreted according to
|
|
the UG380 Configuration User Guide.
|
|
|
|
The tool can be used to derive the initialization, startup and finalization
|
|
sequence as well as the configuration data. The latter is written to a frames
|
|
file which can be used by the bitstream tools such as frames2bit to generate
|
|
a valid bitstream.
|
|
'''
|
|
|
|
import argparse
|
|
from io import StringIO
|
|
|
|
conf_regs = {
|
|
0: "CRC",
|
|
1: "FAR_MAJ",
|
|
2: "FAR_MIN",
|
|
3: "FDRI",
|
|
4: "FDRO",
|
|
5: "CMD",
|
|
6: "CTL",
|
|
7: "MASK",
|
|
8: "STAT",
|
|
9: "LOUT",
|
|
10: "COR1",
|
|
11: "COR2",
|
|
12: "PWRDN_REG",
|
|
13: "FLR",
|
|
14: "IDCODE",
|
|
15: "CWDT",
|
|
16: "HC_OPT_REG",
|
|
18: "CSBO",
|
|
19: "GENERAL1",
|
|
20: "GENERAL2",
|
|
21: "GENERAL3",
|
|
22: "GENERAL4",
|
|
23: "GENERAL5",
|
|
24: "MODE_REG",
|
|
25: "PU_GWE",
|
|
26: "PU_GTS",
|
|
27: "MFWR",
|
|
28: "CCLK_FREQ",
|
|
29: "SEU_OPT",
|
|
30: "EXP_SIGN",
|
|
31: "RDBK_SIGN",
|
|
32: "BOOTSTS",
|
|
33: "EYE_MASK",
|
|
34: "CBC_REG"
|
|
}
|
|
|
|
cmd_reg_codes = {
|
|
0: "NULL",
|
|
1: "WCFG",
|
|
2: "MFW",
|
|
3: "LFRM",
|
|
4: "RCFG",
|
|
5: "START",
|
|
7: "RCRC",
|
|
8: "AGHIGH",
|
|
10: "GRESTORE",
|
|
11: "SHUTDOWN",
|
|
13: "DESYNC",
|
|
14: "IPROG"
|
|
}
|
|
|
|
opcodes = ("NOP", "READ", "WRITE", "UNKNOWN")
|
|
|
|
|
|
def KnuthMorrisPratt(text, pattern):
|
|
'''
|
|
Yields all starting positions of copies of the pattern in the text.
|
|
Calling conventions are similar to string.find, but its arguments can be
|
|
lists or iterators, not just strings, it returns all matches, not just
|
|
the first one, and it does not need the whole text in memory at once.
|
|
Whenever it yields, it will have read the text exactly up to and including
|
|
the match that caused the yield.
|
|
'''
|
|
|
|
# allow indexing into pattern and protect against change during yield
|
|
pattern = list(pattern)
|
|
|
|
# build table of shift amounts
|
|
shifts = [1] * (len(pattern) + 1)
|
|
shift = 1
|
|
for pos in range(len(pattern)):
|
|
while shift <= pos and pattern[pos] != pattern[pos - shift]:
|
|
shift += shifts[pos - shift]
|
|
shifts[pos + 1] = shift
|
|
|
|
# do the actual search
|
|
startPos = 0
|
|
matchLen = 0
|
|
for c in text:
|
|
while matchLen == len(pattern) or \
|
|
matchLen >= 0 and pattern[matchLen] != c:
|
|
startPos += shifts[matchLen]
|
|
matchLen -= shifts[matchLen]
|
|
matchLen += 1
|
|
if matchLen == len(pattern):
|
|
yield startPos
|
|
|
|
|
|
class Bitstream:
|
|
def __init__(self, file_name, verbose=False):
|
|
self.frame_data = []
|
|
self.idcode = 0
|
|
self.exp_sign = 0
|
|
self.far_min = 0
|
|
self.far_maj = 0
|
|
self.curr_fdri_write_len = 0
|
|
self.curr_crc_check = 0
|
|
self.fdri_in_progress = False
|
|
with open(file_name, "rb") as f:
|
|
self.bytes = f.read()
|
|
pos, self.header = self.get_header()
|
|
self.body = [
|
|
(i << 8) | j
|
|
for i, j in zip(self.bytes[pos::2], self.bytes[pos + 1::2])
|
|
]
|
|
self.parse_bitstream(verbose)
|
|
|
|
def get_header(self):
|
|
pos = next(KnuthMorrisPratt(self.bytes, [0xaa, 0x99, 0x55, 0x66]))
|
|
return pos + 4, self.bytes[:pos + 4]
|
|
|
|
def parse_bitstream(self, verbose):
|
|
payload_len = 0
|
|
for word in self.body:
|
|
if payload_len > 0:
|
|
if verbose:
|
|
print("\tWord: ", hex(word))
|
|
payload_len = self.parse_reg(
|
|
reg_addr, word, payload_len, verbose)
|
|
continue
|
|
else:
|
|
packet_header = self.parse_packet_header(word)
|
|
opcode = packet_header["opcode"]
|
|
reg_addr = packet_header["reg_addr"]
|
|
words = packet_header["word_count"]
|
|
type = packet_header["type"]
|
|
if verbose:
|
|
print(
|
|
"\tWord: ", hex(word),
|
|
'Type: {}, Op: {}, Addr: {}, Words: {}'.format(
|
|
type, opcodes[opcode], reg_addr, words))
|
|
if opcode and reg_addr in conf_regs:
|
|
payload_len = words
|
|
continue
|
|
|
|
def parse_packet_header(self, word):
|
|
type = (word >> 13) & 0x7
|
|
opcode = (word >> 11) & 0x3
|
|
reg_addr = (word >> 5) & 0x3F
|
|
if type == 1:
|
|
word_count = word & 0x1F
|
|
elif type == 2:
|
|
word_count = 2
|
|
else:
|
|
word_count = 0
|
|
return {
|
|
"type": type,
|
|
"opcode": opcode,
|
|
"reg_addr": reg_addr,
|
|
"word_count": word_count
|
|
}
|
|
|
|
def parse_command(self, word):
|
|
return cmd_reg_codes[word]
|
|
|
|
def parse_cor1(self, word):
|
|
return word
|
|
|
|
def parse_cor2(self, word):
|
|
return word
|
|
|
|
def parse_ctl(self, word):
|
|
#decryption
|
|
dec = (word >> 6) & 1
|
|
#security bits
|
|
sb = (word >> 4) & 3
|
|
#persist
|
|
p = (word >> 3) & 1
|
|
#use efuse
|
|
efuse = (word >> 2) & 1
|
|
#crc extstat disable
|
|
crc = (word >> 1) & 1
|
|
return {
|
|
"decryption": dec,
|
|
"security bits": sb,
|
|
"pesist": p,
|
|
"use efuse": efuse,
|
|
"crc extstat disable": crc
|
|
}
|
|
|
|
def parse_cclk_freq(self, word):
|
|
ext_mclk = (word >> 14) & 1
|
|
mclk_freq = word & 0x3FF
|
|
return (ext_mclk, mclk_freq)
|
|
|
|
def parse_pwrdn(self, word):
|
|
en_eyes = (word >> 14) & 1
|
|
filter_b = (word >> 5) & 1
|
|
en_pgsr = (word >> 4) & 1
|
|
en_pwrdn = (word >> 2) & 1
|
|
keep_sclk = word & 1
|
|
return {
|
|
"en_eyes": en_eyes,
|
|
"filter_b": filter_b,
|
|
"en_pgsr": en_pgsr,
|
|
"en_pwrdn": en_pwrdn,
|
|
"keep_sclk": keep_sclk
|
|
}
|
|
|
|
def parse_eye_mask(self, word):
|
|
return word & 0xFF
|
|
|
|
def parse_hc_opt(self, word):
|
|
return (word >> 6) & 1
|
|
|
|
def parse_cwdt(self, word):
|
|
return word
|
|
|
|
def parse_pu_gwe(self, word):
|
|
return word & 0x3FF
|
|
|
|
def parse_pu_gts(self, word):
|
|
return word & 0x3FF
|
|
|
|
def parse_mode(self, word):
|
|
new_mode = (word >> 13) & 0x1
|
|
buswidth = (word >> 11) & 0x3
|
|
bootmode = (word >> 8) & 0x7
|
|
bootvsel = word & 0xFF
|
|
return {
|
|
"new_mode": new_mode,
|
|
"buswidth": buswidth,
|
|
"bootmode": bootmode,
|
|
"bootvsel": bootvsel
|
|
}
|
|
|
|
def parse_seu(self, word):
|
|
seu_freq = (word >> 4) & 0x3FF
|
|
seu_run_on_err = (word >> 3) & 0x1
|
|
glut_mask = (word >> 1) & 0x1
|
|
seu_enable = word & 0x1
|
|
return {
|
|
"seu_freq": seu_freq,
|
|
"seu_run_on_err": seu_run_on_err,
|
|
"glut_mask": glut_mask,
|
|
"seu_enable": seu_enable
|
|
}
|
|
|
|
def parse_reg(self, reg_addr, word, payload_len, verbose):
|
|
reg = conf_regs[reg_addr]
|
|
if reg == "CMD":
|
|
command = self.parse_command(word)
|
|
if verbose:
|
|
print("Command: {}\n".format(command))
|
|
elif reg == "FLR":
|
|
frame_length = word
|
|
if verbose:
|
|
print("Frame length: {}\n".format(frame_length))
|
|
elif reg == "COR1":
|
|
conf_options = self.parse_cor1(word)
|
|
if verbose:
|
|
print("COR1 options: {}\n".format(conf_options))
|
|
elif reg == "COR2":
|
|
conf_options = self.parse_cor2(word)
|
|
if verbose:
|
|
print("COR2 options: {}\n".format(conf_options))
|
|
elif reg == "IDCODE":
|
|
assert payload_len < 3
|
|
if payload_len == 2:
|
|
self.idcode = word << 16
|
|
elif payload_len == 1:
|
|
self.idcode |= word
|
|
if verbose:
|
|
print("IDCODE: {}\n".format(hex(self.idcode)))
|
|
elif reg == "MASK":
|
|
mask = word
|
|
if verbose:
|
|
print("Mask value: {}\n".format(mask))
|
|
elif reg == "CTL":
|
|
ctl_options = self.parse_ctl(word)
|
|
if verbose:
|
|
print("CTL options: {}\n".format(ctl_options))
|
|
elif reg == "CCLK_FREQ":
|
|
cclk_freq_options = self.parse_cclk_freq(word)
|
|
if verbose:
|
|
print("CCLK_FREQ options: {}\n".format(cclk_freq_options))
|
|
elif reg == "PWRDN_REG":
|
|
suspend_reg_options = self.parse_pwrdn(word)
|
|
if verbose:
|
|
print("{} options: {}\n".format(reg, suspend_reg_options))
|
|
elif reg == "EYE_MASK":
|
|
eye_mask = self.parse_eye_mask(word)
|
|
if verbose:
|
|
print("{} options: {}\n".format(reg, eye_mask))
|
|
elif reg == "HC_OPT_REG":
|
|
hc_options = self.parse_hc_opt(word)
|
|
if verbose:
|
|
print("{} options: {}\n".format(reg, hc_options))
|
|
elif reg == "CWDT":
|
|
cwdt_options = self.parse_cwdt(word)
|
|
if verbose:
|
|
print("{} options: {}\n".format(reg, cwdt_options))
|
|
elif reg == "PU_GWE":
|
|
pu_gwe_sequence = self.parse_pu_gwe(word)
|
|
if verbose:
|
|
print("{} options: {}\n".format(reg, pu_gwe_sequence))
|
|
elif reg == "PU_GTS":
|
|
pu_gts_sequence = self.parse_pu_gts(word)
|
|
if verbose:
|
|
print("{} options: {}\n".format(reg, pu_gts_sequence))
|
|
elif reg == "MODE_REG":
|
|
mode_options = self.parse_mode(word)
|
|
if verbose:
|
|
print("{} options: {}\n".format(reg, mode_options))
|
|
elif reg == "GENERAL1" or reg == "GENERAL2" \
|
|
or reg == "GENERAL3" or reg == "GENERAL4" \
|
|
or reg == "GENERAL5":
|
|
general_options = word
|
|
if verbose:
|
|
print("{} options: {}\n".format(reg, general_options))
|
|
elif reg == "SEU_OPT":
|
|
seu_options = self.parse_seu(word)
|
|
if verbose:
|
|
print("{} options: {}\n".format(reg, seu_options))
|
|
elif reg == "EXP_SIGN":
|
|
if payload_len == 2:
|
|
self.exp_sign = word << 16
|
|
elif payload_len == 1:
|
|
self.exp_sign |= word
|
|
if verbose:
|
|
print("{}: {}\n".format(reg, self.exp_sign))
|
|
elif reg == "FAR_MAJ":
|
|
if payload_len == 2:
|
|
self.current_far_maj = word
|
|
elif payload_len == 1:
|
|
self.current_far_min = word
|
|
if verbose:
|
|
print(
|
|
"{}: {} FAR_MIN: {}\n".format(
|
|
reg, self.far_maj, self.far_min))
|
|
elif reg == "FDRI":
|
|
if self.fdri_in_progress:
|
|
self.frame_data.append(word)
|
|
if payload_len == 1:
|
|
self.fdri_in_progress = False
|
|
return 0
|
|
elif payload_len == 2:
|
|
self.curr_fdri_write_len = (word & 0xFFF) << 16
|
|
elif payload_len == 1:
|
|
self.curr_fdri_write_len |= word
|
|
self.fdri_in_progress = True
|
|
# Check if 0 words actually means read something
|
|
payload_len = self.curr_fdri_write_len + 2
|
|
if verbose:
|
|
print("{}: {}\n".format(reg, self.curr_fdri_write_len))
|
|
return payload_len
|
|
elif reg == "CRC":
|
|
if payload_len == 2:
|
|
self.curr_crc_check = (word & 0xFFF) << 16
|
|
elif payload_len == 1:
|
|
self.curr_crc_check |= word
|
|
if verbose:
|
|
print("{}: {}\n".format(reg, self.curr_crc_check))
|
|
payload_len -= 1
|
|
return payload_len
|
|
|
|
def write_frames_txt(self, file_name):
|
|
'''Write frame data in a more readable format'''
|
|
frame_stream = StringIO()
|
|
for i in range(len(self.frame_data)):
|
|
if i % 65 == 0:
|
|
frame_stream.write("\nFrame {:4}\n".format(i // 65))
|
|
#IOB word
|
|
if i % 65 == 32:
|
|
frame_stream.write(
|
|
"\n#{:3}:{:6}\n".format(i % 65, hex(self.frame_data[i])))
|
|
else:
|
|
frame_stream.write(
|
|
"#{:3}:{:6},".format(i % 65, hex(self.frame_data[i])))
|
|
with open(file_name, "w") as f:
|
|
print(frame_stream.getvalue(), file=f)
|
|
|
|
def write_frames(self, file_name):
|
|
'''Write configuration data to frames file'''
|
|
frame_stream = StringIO()
|
|
for i in range(len(self.frame_data)):
|
|
if i % 65 == 0:
|
|
frame_stream.write("0x{:08x} ".format(i // 65))
|
|
frame_stream.write("0x{:04x}".format(self.frame_data[i]))
|
|
if i % 65 == 64:
|
|
frame_stream.write("\n")
|
|
elif i < len(self.frame_data) - 1:
|
|
frame_stream.write(",")
|
|
with open(file_name, "w") as f:
|
|
print(frame_stream.getvalue(), file=f)
|
|
|
|
|
|
def main(args):
|
|
verbose = not args.silent
|
|
bitstream = Bitstream(args.bitstream, verbose)
|
|
print("Frame data length: ", len(bitstream.frame_data))
|
|
if args.frames_out:
|
|
bitstream.write_frames(args.frames_out)
|
|
if verbose:
|
|
bitstream.write_frames_txt(args.frames_out + ".txt")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--bitstream', help='Input bitstream')
|
|
parser.add_argument('--frames_out', help='Output frames file')
|
|
parser.add_argument(
|
|
'--silent', help="Don't print analysis details", action='store_true')
|
|
args = parser.parse_args()
|
|
main(args)
|