|  | #!/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 | 
|  | """ Sanity checks FASM output from IOB fuzzer. | 
|  | The IOB fuzzer is fairly complicated, and it's output is hard to verify by | 
|  | inspected.  For this reason, check_results.py was written to compare the | 
|  | specimen's generated and their FASM output.  The FASM output does pose a | 
|  | chicken and egg issue.  The test procedure is a follows: | 
|  |  | 
|  | 1. Build the database (e.g. make -j<N> run) | 
|  | 2. Build the database again (e.g. make -j<N> run) | 
|  | 3. Run check_results.py | 
|  |  | 
|  | The second time that the database is run, the FASM files in the specimen's | 
|  | will have the bits documented by fuzzer. | 
|  |  | 
|  | """ | 
|  | import argparse | 
|  | import os | 
|  | import os.path | 
|  | from prjxray import verilog | 
|  | import json | 
|  | import generate | 
|  |  | 
|  |  | 
|  | def process_parts(parts): | 
|  | if len(parts) == 0: | 
|  | return | 
|  |  | 
|  | if parts[-1] == 'IN_ONLY': | 
|  | yield 'type', ['IBUF', 'IBUFDS'] | 
|  |  | 
|  | if len(parts) > 2 and parts[-2] == 'SLEW': | 
|  | yield 'SLEW', verilog.quote(parts[-1]) | 
|  |  | 
|  | if parts[0] == 'PULLTYPE': | 
|  | yield 'PULLTYPE', verilog.quote(parts[1]) | 
|  |  | 
|  | if len(parts) > 1 and parts[1] == 'IN': | 
|  | yield 'IOSTANDARDS', parts[0].split('_') | 
|  | yield 'IN', True | 
|  |  | 
|  | if len(parts) > 1 and parts[1] == 'IN_DIFF': | 
|  | yield 'IOSTANDARDS', parts[0].split('_') | 
|  | yield 'IN_DIFF', True | 
|  |  | 
|  | if len(parts) > 1 and parts[1] == 'DRIVE': | 
|  | yield 'IOSTANDARDS', parts[0].split('_') | 
|  |  | 
|  | if parts[2] == 'I_FIXED': | 
|  | yield 'DRIVES', [None] | 
|  | else: | 
|  | yield 'DRIVES', parts[2].split('_') | 
|  |  | 
|  |  | 
|  | def create_sites_from_fasm(fasm_file): | 
|  | sites = {} | 
|  |  | 
|  | diff_tiles = set() | 
|  |  | 
|  | with open(fasm_file) as f: | 
|  | for l in f: | 
|  | if 'IOB33' not in l: | 
|  | continue | 
|  |  | 
|  | parts = l.strip().split('.') | 
|  | tile = parts[0] | 
|  | site = parts[1] | 
|  |  | 
|  | if 'OUT_DIFF' == site: | 
|  | diff_tiles.add(tile) | 
|  | continue | 
|  |  | 
|  | if (tile, site) not in sites: | 
|  | sites[(tile, site)] = { | 
|  | 'tile': tile, | 
|  | 'site_key': site, | 
|  | } | 
|  |  | 
|  | if len(parts) > 3 and 'IN_DIFF' == parts[3]: | 
|  | diff_tiles.add(tile) | 
|  |  | 
|  | for key, value in process_parts(parts[2:]): | 
|  | sites[(tile, site)][key] = value | 
|  |  | 
|  | for key in sites: | 
|  | if 'type' not in sites[key]: | 
|  | if 'IOSTANDARDS' not in sites[key]: | 
|  | sites[key]['type'] = [None] | 
|  | else: | 
|  | assert 'IOSTANDARDS' in sites[key], sites[key] | 
|  | assert 'DRIVES' in sites[key], sites[key] | 
|  |  | 
|  | if 'IN' in sites[key]: | 
|  | sites[key]['type'] = ['IOBUF', 'IOBUF_INTERMDISABLE'] | 
|  | else: | 
|  | sites[key]['type'] = [ | 
|  | "OBUF", | 
|  | "OBUFDS", | 
|  | "OBUFTDS", | 
|  | "OBUFDS_DUAL_BUF", | 
|  | "OBUFTDS_DUAL_BUF", | 
|  | ] | 
|  |  | 
|  | return sites, diff_tiles | 
|  |  | 
|  |  | 
|  | def process_specimen(fasm_file, params_json): | 
|  | sites, diff_tiles = create_sites_from_fasm(fasm_file) | 
|  |  | 
|  | with open(params_json) as f: | 
|  | params = json.load(f) | 
|  |  | 
|  | count = 0 | 
|  | for p in params['tiles']: | 
|  | tile = p['tile'] | 
|  | for site in p['site'].split(' '): | 
|  | site_y = int(site[site.find('Y') + 1:]) % 2 | 
|  |  | 
|  | if generate.skip_broken_tiles(p): | 
|  | continue | 
|  |  | 
|  | site_key = 'IOB_Y{}'.format(site_y) | 
|  |  | 
|  | if (tile, site_key) not in sites: | 
|  | assert p['type'] is None, p | 
|  | continue | 
|  |  | 
|  | site_from_fasm = sites[(tile, site_key)] | 
|  |  | 
|  | if site_y == 0 or tile not in diff_tiles: | 
|  | assert p['type'] in site_from_fasm['type'], ( | 
|  | tile, site_key, p['type'], site_from_fasm['type']) | 
|  | else: | 
|  | # Y1 on DIFF tiles is always none. | 
|  | assert p['type'] is None, p | 
|  |  | 
|  | if p['type'] is None: | 
|  | continue | 
|  |  | 
|  | assert 'PULLTYPE' in p, p | 
|  | assert 'PULLTYPE' in site_from_fasm, site_from_fasm | 
|  |  | 
|  | if verilog.unquote(p['PULLTYPE']) == '': | 
|  | # Default is None. | 
|  | pulltype = verilog.quote('NONE') | 
|  | else: | 
|  | pulltype = p['PULLTYPE'] | 
|  |  | 
|  | assert pulltype == site_from_fasm['PULLTYPE'], ( | 
|  | tile, site_key, p, site_from_fasm) | 
|  |  | 
|  | assert 'IOSTANDARDS' in site_from_fasm, (tile, site) | 
|  |  | 
|  | iostandard = verilog.unquote(p['IOSTANDARD']) | 
|  | if iostandard.startswith('DIFF_'): | 
|  | iostandard = iostandard[5:] | 
|  |  | 
|  | assert iostandard in site_from_fasm['IOSTANDARDS'], ( | 
|  | p['IOSTANDARD'], | 
|  | site_from_fasm['IOSTANDARDS'], | 
|  | ) | 
|  |  | 
|  | if p['type'] not in ['IBUF', 'IBUFDS']: | 
|  | if verilog.unquote(p['SLEW']) == '': | 
|  | # Default is None. | 
|  | slew = verilog.quote('SLOW') | 
|  | else: | 
|  | slew = p['SLEW'] | 
|  |  | 
|  | assert slew == site_from_fasm['SLEW'], ( | 
|  | tile, site_key, p, site_from_fasm) | 
|  |  | 
|  | assert 'DRIVES' not in p, p | 
|  | assert 'DRIVES' in site_from_fasm, ( | 
|  | tile, site, p['type'], site_from_fasm) | 
|  |  | 
|  | if p['DRIVE'] is None: | 
|  | assert None in site_from_fasm['DRIVES'], ( | 
|  | tile, site_key, p['DRIVE'], site_from_fasm['DRIVES']) | 
|  | elif p['DRIVE'] is '': | 
|  | if None in site_from_fasm['DRIVES']: | 
|  | # IOSTANDARD has not DRIVE setting, ignore | 
|  | pass | 
|  | else: | 
|  | # Check that drive is at default | 
|  | assert 'I12' in site_from_fasm['DRIVES'], ( | 
|  | tile, site_key, p['DRIVE'], | 
|  | site_from_fasm['DRIVES']) | 
|  | else: | 
|  | assert 'I{}'.format( | 
|  | p['DRIVE']) in site_from_fasm['DRIVES'], ( | 
|  | tile, site_key, p['DRIVE'], | 
|  | site_from_fasm['DRIVES']) | 
|  |  | 
|  | count += 1 | 
|  |  | 
|  | return count | 
|  |  | 
|  |  | 
|  | def scan_specimens(): | 
|  | for root, dirs, files in os.walk('build'): | 
|  | if os.path.basename(root).startswith('specimen_'): | 
|  | print('Processing', os.path.basename(root)) | 
|  | process_specimen( | 
|  | fasm_file=os.path.join(root, 'design.fasm'), | 
|  | params_json=os.path.join(root, 'params.json')) | 
|  |  | 
|  | print('No errors found!') | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser(description="Verify IOB FASM vs BELs.") | 
|  |  | 
|  | parser.add_argument('--fasm') | 
|  | parser.add_argument('--params') | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | if not args.fasm and not args.params: | 
|  | scan_specimens() | 
|  | else: | 
|  | count = process_specimen(fasm_file=args.fasm, params_json=args.params) | 
|  | print('No errors found in {} IO sites'.format(count)) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |