| #!/usr/bin/env python3 | 
 | # -*- coding: utf-8 -*- | 
 | # | 
 | # Copyright (C) 2017-2022  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 | 
 | inspection.  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 'IOB18' 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_DCIEN'] | 
 |                 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'] == '': | 
 |                     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() |