|  | #!/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 performs the analysis of disassembled bitstream and design information. | 
|  | It correlates presence/absence of particular fasm features in the bitstream | 
|  | with presence/absence of a particular IOB type. It also checks for features | 
|  | that are set for unused IOBs that are in the same bank as the used ones. | 
|  |  | 
|  | The list of available fasm features is read from the prjxray database. The | 
|  | analysis result is written to a CSV file. | 
|  | """ | 
|  | import os | 
|  | import argparse | 
|  | import json | 
|  | import random | 
|  |  | 
|  | from collections import defaultdict | 
|  |  | 
|  | from prjxray import util | 
|  | import fasm | 
|  |  | 
|  | # ============================================================================= | 
|  |  | 
|  |  | 
|  | def load_iob_segbits(): | 
|  | """ | 
|  | Loads IOB segbits from the database to get fasm feature list. | 
|  | This function assumes: | 
|  | - LIOB33/RIOB33 have the same features. | 
|  | - IOB_Y0 and IOB_Y1 have the same features (at least those of interest). | 
|  | """ | 
|  | features = [] | 
|  |  | 
|  | fname = os.path.join(util.get_db_root(), "segbits_liob33.db") | 
|  | with open(fname, "r") as fp: | 
|  | for line in fp: | 
|  |  | 
|  | line = line.strip() | 
|  | if len(line) == 0: | 
|  | continue | 
|  |  | 
|  | # Parse the line | 
|  | fields = line.split(maxsplit=1) | 
|  | feature = fields[0] | 
|  | bits = fields[1] | 
|  |  | 
|  | # Parse the feature | 
|  | parts = feature.split(".", maxsplit=3) | 
|  | if len(parts) >= 3 and parts[1] == "IOB_Y0": | 
|  | features.append(".".join(parts[2:])) | 
|  |  | 
|  | return features | 
|  |  | 
|  |  | 
|  | def correlate_features(features, tile, site, set_features): | 
|  | """ | 
|  | Correlate each feature with the fasm disassembly for given tile/site. | 
|  |  | 
|  | Given a set of all possible fasm features (for an IOB) in the first | 
|  | argument, checks whether they are set or cleared for the given tile+site in | 
|  | a design. The parameter set_features contains fasm dissassembly of the | 
|  | design. | 
|  |  | 
|  | Returns a list of tuples with feature names and whether they are set or not. | 
|  | """ | 
|  | result = [] | 
|  |  | 
|  | for feature in features: | 
|  | full_feature = "{}.{}.{}".format(tile, site, feature) | 
|  | if full_feature in set_features: | 
|  | result.append(( | 
|  | feature, | 
|  | True, | 
|  | )) | 
|  | else: | 
|  | result.append(( | 
|  | feature, | 
|  | False, | 
|  | )) | 
|  |  | 
|  | return result | 
|  |  | 
|  |  | 
|  | def run(): | 
|  | """ | 
|  | Main. | 
|  | """ | 
|  |  | 
|  | # Parse arguments | 
|  | parser = argparse.ArgumentParser() | 
|  | parser.add_argument( | 
|  | "--design", type=str, required=True, help="Design JSON file") | 
|  | parser.add_argument( | 
|  | "--fasm", type=str, required=True, help="Decoded fasm file") | 
|  | parser.add_argument( | 
|  | "-o", type=str, default="results.csv", help="Output CSV file") | 
|  | parser.add_argument("-j", type=str, default=None, help="Output JSON file") | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | # Load IOB features | 
|  | features = load_iob_segbits() | 
|  |  | 
|  | # Load the design data | 
|  | with open(args.design, "r") as fp: | 
|  | design = json.load(fp) | 
|  |  | 
|  | # Load disassembled fasm | 
|  | fasm_tuples = fasm.parse_fasm_filename(args.fasm) | 
|  | set_features = fasm.fasm_tuple_to_string(fasm_tuples).split("\n") | 
|  |  | 
|  | # Correlate features for given IOB types | 
|  | results = [] | 
|  | for region in design: | 
|  | result = dict(region["iosettings"]) | 
|  |  | 
|  | for l in ["input", "output", "inout", "unused_sites"]: | 
|  |  | 
|  | # TODO: Check if this is true eg. for all unused sites, not just | 
|  | # one random site. | 
|  | tile, site = random.choice(region[l]).split(".") | 
|  | matches = correlate_features(features, tile, site, set_features) | 
|  |  | 
|  | result[l] = matches | 
|  |  | 
|  | results.append(result) | 
|  |  | 
|  | # Save results | 
|  | if args.j: | 
|  | with open(args.j, "w") as fp: | 
|  | json.dump(results, fp, indent=2, sort_keys=True) | 
|  |  | 
|  | # Save results to CSV | 
|  | with open(args.o, "w") as fp: | 
|  | csv_data = defaultdict(lambda: {}) | 
|  |  | 
|  | # Collect data | 
|  | for result in results: | 
|  | iostandard = result["iostandard"] | 
|  | drive = result["drive"] | 
|  | slew = result["slew"] | 
|  |  | 
|  | if drive is None: | 
|  | drive = "_FIXED" | 
|  |  | 
|  | iosettings = "{}.I{}.{}".format(iostandard, drive, slew) | 
|  |  | 
|  | is_diff = "DIFF" in iostandard | 
|  |  | 
|  | for feature in sorted(features): | 
|  | I = [f[1] for f in result["input"] if f[0] == feature and f[1]] | 
|  | O = [ | 
|  | f[1] for f in result["output"] if f[0] == feature and f[1] | 
|  | ] | 
|  | T = [f[1] for f in result["inout"] if f[0] == feature and f[1]] | 
|  | U = [ | 
|  | f[1] | 
|  | for f in result["unused_sites"] | 
|  | if f[0] == feature and f[1] | 
|  | ] | 
|  |  | 
|  | s = "".join( | 
|  | [ | 
|  | "I" if len(I) > 0 else "", | 
|  | "O" if len(O) > 0 else "", | 
|  | "T" if len(T) > 0 else "", | 
|  | "U" if len(U) > 0 else "", | 
|  | ]) | 
|  |  | 
|  | csv_data[iosettings][feature] = s | 
|  |  | 
|  | # Write header | 
|  | line = ["iosettings"] + sorted(features) | 
|  | fp.write(",".join(line) + "\n") | 
|  |  | 
|  | # Write data | 
|  | for iosettings in sorted(csv_data.keys()): | 
|  | data = csv_data[iosettings] | 
|  | line = [iosettings | 
|  | ] + [data[feature] for feature in sorted(features)] | 
|  |  | 
|  | fp.write(",".join(line) + "\n") | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | run() |