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 new file mode 100755 index 00000000..382aa674 --- /dev/null +++ b/utils/sp6_bitstream_analyzer.py @@ -0,0 +1,424 @@ +#!/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)