| #!/usr/bin/env python3 | 
 | # -*- coding: utf-8 -*- | 
 | # | 
 | # Copyright (C) 2017-2020  The Project X-Ray Authors. | 
 | # | 
 | # Use of this source code is governed by a ISC-style | 
 | # license that can be found in the LICENSE file or at | 
 | # https://opensource.org/licenses/ISC | 
 | # | 
 | # SPDX-License-Identifier: ISC | 
 | ''' | 
 | This script allows to load a number of .db or .rdb files and display bits in | 
 | a nice visualization. | 
 |  | 
 | When more than one files are loaded, a difference between them is shown. | 
 | Differring bits are highlighted. | 
 | ''' | 
 | import argparse | 
 | import re | 
 | import sys | 
 |  | 
 | import itertools | 
 |  | 
 | from prjxray.util import OpenSafeFile | 
 |  | 
 | # ============================================================================= | 
 |  | 
 |  | 
 | def tagmap(tag): | 
 |     """ | 
 |     Maps a specific tag name to its generic name | 
 |     """ | 
 |  | 
 |     tag = tag.replace("CLBLL_L", "CLB") | 
 |     tag = tag.replace("CLBLL_M", "CLB") | 
 |     tag = tag.replace("CLBLM_L", "CLB") | 
 |     tag = tag.replace("CLBLM_M", "CLB") | 
 |  | 
 |     tag = tag.replace("SLICEL", "SLICE") | 
 |     tag = tag.replace("SLICEM", "SLICE") | 
 |  | 
 |     tag = tag.replace("LIOB33", "IOB33") | 
 |     tag = tag.replace("RIOB33", "IOB33") | 
 |  | 
 |     tag = tag.replace("LIOI3", "IOI3") | 
 |     tag = tag.replace("RIOI3", "IOI3") | 
 |  | 
 |     # TODO: Add other tag mappings | 
 |  | 
 |     return tag | 
 |  | 
 |  | 
 | def parse_bit(bit): | 
 |     """ | 
 |     Decodes string describing a bit. Returns a tuple (frame, bit, value) | 
 |     """ | 
 |     match = re.match("^(!?)([0-9]+)_([0-9]+)$", bit) | 
 |     assert match != None, bit | 
 |  | 
 |     val = int(match.group(1) != "!") | 
 |     frm = int(match.group(2)) | 
 |     bit = int(match.group(3)) | 
 |  | 
 |     return frm, bit, val | 
 |  | 
 |  | 
 | def load_and_sort_segbits(file_name, tagmap=lambda tag: tag): | 
 |     """ | 
 |     Loads a segbit file (.db or .rdb). Skips bits containing '<' or '>' | 
 |     """ | 
 |  | 
 |     # Load segbits | 
 |     segbits = {} | 
 |     with OpenSafeFile(file_name, "r") as fp: | 
 |         lines = fp.readlines() | 
 |  | 
 |         # Parse lines | 
 |         for line in lines: | 
 |             line = line.strip() | 
 |             fields = line.split() | 
 |  | 
 |             if len(fields) < 2: | 
 |                 print("Malformed line: '%s'" % line) | 
 |                 continue | 
 |  | 
 |             # Map name | 
 |             feature = tagmap(fields[0]) | 
 |  | 
 |             # Decode bits | 
 |             bits = [] | 
 |             for bit in fields[1:]: | 
 |                 if "<" in bit or ">" in bit: | 
 |                     continue | 
 |                 bits.append(parse_bit(bit)) | 
 |  | 
 |             # Sort bits | 
 |             bits.sort(key=lambda bit: (bit[0], bit[1],)) | 
 |             segbits[feature] = bits | 
 |  | 
 |     return segbits | 
 |  | 
 |  | 
 | # ============================================================================= | 
 |  | 
 |  | 
 | def make_header_lines(all_bits): | 
 |     """ | 
 |     Formats header lines | 
 |     """ | 
 |     lines = [] | 
 |  | 
 |     # Bit names | 
 |     bit_names = ["%d_%d" % (b[0], b[1]) for b in all_bits] | 
 |     bit_len = 6 | 
 |     for i in range(bit_len): | 
 |         line = "" | 
 |         for j in range(len(all_bits)): | 
 |             bstr = bit_names[j].ljust(bit_len).replace("_", "|") | 
 |             line += bstr[i] | 
 |         lines.append(line) | 
 |  | 
 |     return lines | 
 |  | 
 |  | 
 | def make_data_lines(all_tags, all_bits, segbits): | 
 |     """ | 
 |     Formats data lines | 
 |     """ | 
 |     lines = [] | 
 |  | 
 |     def map_f(b): | 
 |         if b < 0: return "0" | 
 |         if b > 0: return "1" | 
 |         return "-" | 
 |  | 
 |     # Bit data | 
 |     for tag in all_tags: | 
 |         if tag in segbits.keys(): | 
 |             lines.append("".join(map(map_f, segbits[tag]))) | 
 |         else: | 
 |             lines.append(" " * len(all_bits)) | 
 |  | 
 |     return lines | 
 |  | 
 |  | 
 | def main(): | 
 |  | 
 |     # Colors for TTY | 
 |     if sys.stdout.isatty(): | 
 |         colors = { | 
 |             "NONE": "\033[0m", | 
 |             "DIFF": "\033[1m", | 
 |         } | 
 |  | 
 |     # Colors for pipe | 
 |     else: | 
 |         colors = { | 
 |             "NONE": "", | 
 |             "DIFF": "", | 
 |         } | 
 |  | 
 |     # ........................................................................ | 
 |  | 
 |     parser = argparse.ArgumentParser( | 
 |         description=__doc__, | 
 |         formatter_class=argparse.RawDescriptionHelpFormatter) | 
 |  | 
 |     parser.add_argument("files", nargs="*", type=str, help="Input files") | 
 |  | 
 |     args = parser.parse_args() | 
 |  | 
 |     # Load segbits | 
 |     all_segbits = [] | 
 |     for f in args.files: | 
 |         all_segbits.append(load_and_sort_segbits(f, tagmap)) | 
 |  | 
 |     # List of all features and all bits | 
 |     all_tags = set() | 
 |     all_bits = set() | 
 |  | 
 |     for segbits in all_segbits: | 
 |         all_tags |= set(segbits.keys()) | 
 |         for bits in segbits.values(): | 
 |             all_bits |= set([(b[0], b[1]) for b in bits]) | 
 |  | 
 |     all_tags = sorted(list(all_tags)) | 
 |     all_bits = sorted(list(all_bits)) | 
 |  | 
 |     # Convert bit lists to bit vectors | 
 |     for segbits in all_segbits: | 
 |         for tag in segbits.keys(): | 
 |             vec = list([0] * len(all_bits)) | 
 |             for i, bit in enumerate(all_bits): | 
 |                 if (bit[0], bit[1], 0) in segbits[tag]: | 
 |                     vec[i] = -1 | 
 |                 if (bit[0], bit[1], 1) in segbits[tag]: | 
 |                     vec[i] = +1 | 
 |             segbits[tag] = vec | 
 |  | 
 |     # Make header and data lines | 
 |     header_lines = make_header_lines(all_bits) | 
 |     data_lines = [ | 
 |         make_data_lines(all_tags, all_bits, segbits) for segbits in all_segbits | 
 |     ] | 
 |  | 
 |     # Display | 
 |     max_tag_len = max([len(tag) for tag in all_tags]) | 
 |  | 
 |     for l in header_lines: | 
 |         line = " " * max_tag_len + " " | 
 |         for i in range(len(all_segbits)): | 
 |             line += l + " " | 
 |         print(line) | 
 |  | 
 |     data_len = len(all_bits) | 
 |     for i, tag in enumerate(all_tags): | 
 |         line = tag.ljust(max_tag_len) + " " | 
 |  | 
 |         diff = bytearray(data_len) | 
 |         for l1, l2 in itertools.combinations(data_lines, 2): | 
 |             for j in range(data_len): | 
 |                 if l1[i][j] != l2[i][j]: | 
 |                     diff[j] = 1 | 
 |  | 
 |         for l in data_lines: | 
 |             for j in range(data_len): | 
 |                 if diff[j]: | 
 |                     line += colors["DIFF"] + l[i][j] + colors["NONE"] | 
 |                 else: | 
 |                     line += l[i][j] | 
 |             line += " " | 
 |  | 
 |         print(line) | 
 |  | 
 |  | 
 | # ============================================================================= | 
 |  | 
 | if __name__ == "__main__": | 
 |     main() |