blob: 473bb6aca8f8d72898044d6cfb77060dd065fe1f [file] [log] [blame] [edit]
#!/usr/bin/env python3
'''
UltraScalePlus bitstream analyzer tool.
This script reads a UltraScalePlus 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 UG570 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",
2: "FDRI",
3: "FDRO",
4: "CMD",
5: "CTL0",
6: "MASK",
7: "STAT",
8: "LOUT",
9: "COR0",
10: "MFWR",
11: "CBC",
12: "IDCODE",
13: "AXSS",
14: "COR1",
16: "WBSTAR",
17: "TIMER",
22: "BOOTSTS",
24: "CTL1",
31: "BSPI"
}
cmd_reg_codes = {
0: "NULL",
1: "WCFG",
2: "MFW",
3: "LFRM",
4: "RCFG",
5: "START",
7: "RCRC",
8: "AGHIGH",
9: "SWITCH",
10: "GRESTORE",
11: "SHUTDOWN",
13: "DESYNC",
15: "IPROG",
16: "CRCC",
17: "LTIMER",
18: "BSPI_READ",
19: "FALL_EDGE"
}
opcodes = ("NOP", "READ", "WRITE", "UNKNOWN")
class Bitstream:
def __init__(self, file_name, verbose=False):
self.frame_data = []
self.fdri_write_len = 0
self.fdri_in_progress = False
self.words = []
with open(file_name, "rb") as f:
word = f.read(4)
while word:
self.words.append(int.from_bytes(word, byteorder='big'))
word = f.read(4)
pos, self.header = self.get_header()
self.body = self.words[pos + 1:]
self.parse_bitstream(verbose)
def get_header(self):
'''Return position and content of header'''
pos = self.words.index(0xaa995566)
return pos, self.words[:pos + 1]
def parse_bitstream(self, verbose):
payload_len = 0
for word in self.body:
if payload_len > 0:
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:
if not opcode:
print("\n\tNOP")
else:
print(
"\n\tConfiguration Register Word: ", hex(word),
'Type: {}, Op: {}, Addr: {} ({}), Words: {}'.
format(
type, opcodes[opcode], conf_regs[reg_addr]
if reg_addr in conf_regs else "UNKNOWN",
reg_addr, words))
if opcode and reg_addr in conf_regs:
payload_len = words
if conf_regs[reg_addr] == "FDRI" and type == 1:
self.fdri_in_progress = True
self.fdri_write_len = payload_len
continue
def parse_packet_header(self, word):
type = (word >> 29) & 0x7
opcode = (word >> 27) & 0x3
reg_addr = (word >> 13) & 0x1F
if type == 1:
word_count = word & 0x7FF
elif type == 2:
word_count = word & 0x7FFFFFF
else:
word_count = 0
return {
"type": type,
"opcode": opcode,
"reg_addr": reg_addr,
"word_count": word_count
}
def parse_command(self, word, verbose):
if verbose:
print("\tCommand: {} ({})".format(cmd_reg_codes[word], word))
#TODO Add COR0 options parsing
def parse_cor0(self, word, verbose):
if verbose:
print("\tCOR0 options: {:08X}".format(word))
def parse_reg(self, reg_addr, word, payload_len, verbose):
assert reg_addr in conf_regs
reg = conf_regs[reg_addr]
if reg == "CMD":
self.parse_command(word, verbose)
elif reg == "COR0":
self.parse_cor0(word, verbose)
elif reg == "FDRI":
# We are in progress of a FDRI operation
# Keep adding data words
if self.fdri_in_progress:
if verbose:
print("\t{:2d}. 0x{:08X}".format(
self.fdri_write_len - payload_len, word))
self.frame_data.append(word)
if payload_len == 1:
self.fdri_in_progress = False
return 0
else:
#FIXME add Type 2 FDRI writes
assert False
#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
#if verbose:
# print("\t{}: {}\n".format(reg, self.curr_fdri_write_len))
#return payload_len
else:
if verbose:
print("\tRegister: {} Value: 0x{:08X}".format(reg, word))
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 % 93 == 0:
frame_stream.write("\nFrame {:4}\n".format(i // 93))
with open(file_name, "w") as f:
print(frame_stream.getvalue(), file=f)
#FIXME Add storing of frame address
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 % 93 == 0:
frame_stream.write("0x{:08x} ".format(i // 93))
frame_stream.write("0x{:04x}".format(self.frame_data[i]))
if i % 93 == 92:
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)
#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)