From d59247d9c6b9494adca7d7081dc936f2693362fe Mon Sep 17 00:00:00 2001 From: Tomasz Michalak Date: Wed, 18 Sep 2019 10:18:26 +0200 Subject: [PATCH 1/3] utils: Add Spartan6 bitstream analyzer tool Signed-off-by: Tomasz Michalak --- utils/sp6_bitstream_analyzer.py | 420 ++++++++++++++++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100755 utils/sp6_bitstream_analyzer.py diff --git a/utils/sp6_bitstream_analyzer.py b/utils/sp6_bitstream_analyzer.py new file mode 100755 index 00000000..841ac64a --- /dev/null +++ b/utils/sp6_bitstream_analyzer.py @@ -0,0 +1,420 @@ +#!/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), + f'Type: {type}, Op: {opcodes[opcode]}, Addr: {reg_addr}, Words: {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(f"Command: {command}\n") + elif reg == "FLR": + frame_length = word + if verbose: + print(f"Frame length: {frame_length}\n") + elif reg == "COR1": + conf_options = self.parse_cor1(word) + if verbose: + print(f"COR1 options: {conf_options}\n") + elif reg == "COR2": + conf_options = self.parse_cor2(word) + if verbose: + print(f"COR2 options: {conf_options}\n") + 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(f"Mask value: {mask}\n") + elif reg == "CTL": + ctl_options = self.parse_ctl(word) + if verbose: + print(f"CTL options: {ctl_options}\n") + elif reg == "CCLK_FREQ": + cclk_freq_options = self.parse_cclk_freq(word) + if verbose: + print(f"CCLK_FREQ options: {cclk_freq_options}\n") + elif reg == "PWRDN_REG": + suspend_reg_options = self.parse_pwrdn(word) + if verbose: + print(f"{reg} options: {suspend_reg_options}\n") + elif reg == "EYE_MASK": + eye_mask = self.parse_eye_mask(word) + if verbose: + print(f"{reg} options: {eye_mask}\n") + elif reg == "HC_OPT_REG": + hc_options = self.parse_hc_opt(word) + if verbose: + print(f"{reg} options: {hc_options}\n") + elif reg == "CWDT": + cwdt_options = self.parse_cwdt(word) + if verbose: + print(f"{reg} options: {cwdt_options}\n") + elif reg == "PU_GWE": + pu_gwe_sequence = self.parse_pu_gwe(word) + if verbose: + print(f"{reg} options: {pu_gwe_sequence}\n") + elif reg == "PU_GTS": + pu_gts_sequence = self.parse_pu_gts(word) + if verbose: + print(f"{reg} options: {pu_gts_sequence}\n") + elif reg == "MODE_REG": + mode_options = self.parse_mode(word) + if verbose: + print(f"{reg} options: {mode_options}\n") + elif reg == "GENERAL1" or reg == "GENERAL2" \ + or reg == "GENERAL3" or reg == "GENERAL4" \ + or reg == "GENERAL5": + general_options = word + if verbose: + print(f"{reg} options: {general_options}\n") + elif reg == "SEU_OPT": + seu_options = self.parse_seu(word) + if verbose: + print(f"{reg} options: {seu_options}\n") + elif reg == "EXP_SIGN": + if payload_len == 2: + self.exp_sign = word << 16 + elif payload_len == 1: + self.exp_sign |= word + if verbose: + print(f"{reg}: {self.exp_sign}\n") + 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(f"{reg}: {self.far_maj} FAR_MIN: {self.far_min}\n") + 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(f"{reg}: {self.curr_fdri_write_len}\n") + 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(f"{reg}: {self.curr_crc_check}\n") + 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) From 8054f88d40d6f9388e590c06fca6d4901f9ecf43 Mon Sep 17 00:00:00 2001 From: Tomasz Michalak Date: Thu, 19 Sep 2019 11:44:11 +0200 Subject: [PATCH 2/3] utils: sp6_bitstream_analyzer: Convert f-string to str.format() Signed-off-by: Tomasz Michalak --- utils/sp6_bitstream_analyzer.py | 46 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/utils/sp6_bitstream_analyzer.py b/utils/sp6_bitstream_analyzer.py index 841ac64a..2f273e1e 100755 --- a/utils/sp6_bitstream_analyzer.py +++ b/utils/sp6_bitstream_analyzer.py @@ -144,8 +144,8 @@ class Bitstream: if verbose: print( "\tWord: ", hex(word), - f'Type: {type}, Op: {opcodes[opcode]}, Addr: {reg_addr}, Words: {words}' - ) + 'Type: {}, Op: {}, Addr: {}, Words: {}'.format( + type, opcodes[opcode], reg_addr, words)) if opcode and reg_addr in conf_regs: payload_len = words continue @@ -258,19 +258,19 @@ class Bitstream: if reg == "CMD": command = self.parse_command(word) if verbose: - print(f"Command: {command}\n") + print("Command: {}\n".format(command)) elif reg == "FLR": frame_length = word if verbose: - print(f"Frame length: {frame_length}\n") + print("Frame length: {}\n".format(frame_length)) elif reg == "COR1": conf_options = self.parse_cor1(word) if verbose: - print(f"COR1 options: {conf_options}\n") + print("COR1 options: {}\n".format(conf_options)) elif reg == "COR2": conf_options = self.parse_cor2(word) if verbose: - print(f"COR2 options: {conf_options}\n") + print("COR2 options: {}\n".format(conf_options)) elif reg == "IDCODE": assert payload_len < 3 if payload_len == 2: @@ -282,67 +282,69 @@ class Bitstream: elif reg == "MASK": mask = word if verbose: - print(f"Mask value: {mask}\n") + print("Mask value: {}\n".format(mask)) elif reg == "CTL": ctl_options = self.parse_ctl(word) if verbose: - print(f"CTL options: {ctl_options}\n") + print("CTL options: {}\n".format(ctl_options)) elif reg == "CCLK_FREQ": cclk_freq_options = self.parse_cclk_freq(word) if verbose: - print(f"CCLK_FREQ options: {cclk_freq_options}\n") + print("CCLK_FREQ options: {}\n".format(cclk_freq_options)) elif reg == "PWRDN_REG": suspend_reg_options = self.parse_pwrdn(word) if verbose: - print(f"{reg} options: {suspend_reg_options}\n") + print("{} options: {}\n".format(reg, suspend_reg_options)) elif reg == "EYE_MASK": eye_mask = self.parse_eye_mask(word) if verbose: - print(f"{reg} options: {eye_mask}\n") + print("{} options: {}\n".format(reg, eye_mask)) elif reg == "HC_OPT_REG": hc_options = self.parse_hc_opt(word) if verbose: - print(f"{reg} options: {hc_options}\n") + print("{} options: {}\n".format(reg, hc_options)) elif reg == "CWDT": cwdt_options = self.parse_cwdt(word) if verbose: - print(f"{reg} options: {cwdt_options}\n") + print("{} options: {}\n".format(reg, cwdt_options)) elif reg == "PU_GWE": pu_gwe_sequence = self.parse_pu_gwe(word) if verbose: - print(f"{reg} options: {pu_gwe_sequence}\n") + print("{} options: {}\n".format(reg, pu_gwe_sequence)) elif reg == "PU_GTS": pu_gts_sequence = self.parse_pu_gts(word) if verbose: - print(f"{reg} options: {pu_gts_sequence}\n") + print("{} options: {}\n".format(reg, pu_gts_sequence)) elif reg == "MODE_REG": mode_options = self.parse_mode(word) if verbose: - print(f"{reg} options: {mode_options}\n") + 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(f"{reg} options: {general_options}\n") + print("{} options: {}\n".format(reg, general_options)) elif reg == "SEU_OPT": seu_options = self.parse_seu(word) if verbose: - print(f"{reg} options: {seu_options}\n") + 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(f"{reg}: {self.exp_sign}\n") + 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(f"{reg}: {self.far_maj} FAR_MIN: {self.far_min}\n") + 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) @@ -357,7 +359,7 @@ class Bitstream: # Check if 0 words actually means read something payload_len = self.curr_fdri_write_len + 2 if verbose: - print(f"{reg}: {self.curr_fdri_write_len}\n") + print("{}: {}\n".format(reg, self.curr_fdri_write_len)) return payload_len elif reg == "CRC": if payload_len == 2: @@ -365,7 +367,7 @@ class Bitstream: elif payload_len == 1: self.curr_crc_check |= word if verbose: - print(f"{reg}: {self.curr_crc_check}\n") + print("{}: {}\n".format(reg, self.curr_crc_check)) payload_len -= 1 return payload_len From a9f4399591568cead3e829e87b1ea371b591ca5a Mon Sep 17 00:00:00 2001 From: Tomasz Michalak Date: Wed, 25 Sep 2019 07:39:27 +0200 Subject: [PATCH 3/3] utils: sp6_bitstream_analyzer: Fix formatting Signed-off-by: Tomasz Michalak --- third_party/fasm | 2 +- utils/sp6_bitstream_analyzer.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/third_party/fasm b/third_party/fasm index b8db3651..ddc281a4 160000 --- a/third_party/fasm +++ b/third_party/fasm @@ -1 +1 @@ -Subproject commit b8db36518534a7c204f80d785a893055258205cb +Subproject commit ddc281a41662bff3efb4a66c5b22307e31e5df44 diff --git a/utils/sp6_bitstream_analyzer.py b/utils/sp6_bitstream_analyzer.py index 2f273e1e..382aa674 100755 --- a/utils/sp6_bitstream_analyzer.py +++ b/utils/sp6_bitstream_analyzer.py @@ -72,12 +72,14 @@ 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.''' + ''' + 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)