| #!/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 | 
 | """ IOB bits are more complicated than can be easily expressed to segmaker. | 
 | There are couple cases that need to be handled here: | 
 |  | 
 | - There are some bits that are always set for IN-only ports, but are cleared | 
 |   selectively for OUT and INOUT ports. | 
 | - There are bits per each IOSTANDARD, in addition to drive patterns.  These | 
 |   can be merged to provide unique "(IOSTANDARD, DRIVE)" bit sets. | 
 | """ | 
 | import argparse | 
 |  | 
 |  | 
 | def get_name(l): | 
 |     parts = l.strip().split(' ') | 
 |     return parts[0] | 
 |  | 
 |  | 
 | def get_site(l): | 
 |     return get_name(l).split('.')[1] | 
 |  | 
 |  | 
 | def parse_bits(l): | 
 |     parts = l.strip().split(' ') | 
 |     if parts[1] in ['<0', '<const0>', '<const1>']: | 
 |         return frozenset() | 
 |     else: | 
 |         return frozenset(parts[1:]) | 
 |  | 
 |  | 
 | def filter_negbits(site, feature, bits): | 
 |     lvds_bits = frozenset(['!38_24', "!38_48", "!39_33", '!39_47', '!39_49']) | 
 |     if "IOB_Y0" in feature and not "LVDS" in feature: | 
 |         bits = bits.difference(lvds_bits) | 
 |     if feature.endswith("IOB_Y0.LVCMOS12_LVCMOS15_LVCMOS18.IN"): | 
 |         bits = bits.difference(frozenset(['!39_01'])) | 
 |     if feature.endswith("IOB_Y1.LVCMOS12_LVCMOS15_LVCMOS18.IN"): | 
 |         bits = bits.difference(frozenset(['!38_126'])) | 
 |  | 
 |     return bits | 
 |  | 
 |  | 
 | def process_features_sets(iostandard_lines): | 
 |     sites = {} | 
 |  | 
 |     for iostd_type, iostd_list in iostandard_lines.items(): | 
 |         for iostd_line in iostd_list: | 
 |             feature = get_name(iostd_line) | 
 |             feature_parts = feature.split('.') | 
 |             site = get_site(iostd_line) | 
 |             iostandard = feature_parts[2] | 
 |  | 
 |             bits = parse_bits(iostd_line) | 
 |  | 
 |             key = (site, iostd_type) | 
 |             if key not in sites: | 
 |                 sites[key] = {} | 
 |  | 
 |             group = feature_parts[3] | 
 |             if group not in sites[key]: | 
 |                 sites[key][group] = {} | 
 |  | 
 |             if group in ['DRIVE', 'SLEW']: | 
 |                 enum = feature_parts[4] | 
 |                 sites[key][group][(iostandard, enum)] = bits | 
 |             elif group in ['IN', 'IN_DIFF', 'IN_ONLY', 'IN_USE', 'OUT', | 
 |                            'STEPDOWN', 'ZIBUF_LOW_PWR']: | 
 |                 sites[key][group][(iostandard, None)] = bits | 
 |             else: | 
 |                 assert False, group | 
 |  | 
 |     for site in sites: | 
 |         for iostandard, enum in sites[site]['DRIVE']: | 
 |             sites[site]['DRIVE'][(iostandard, enum)] |= sites[site]['OUT'][( | 
 |                 iostandard, None)] | 
 |  | 
 |         for iostandard, enum in sites[site]['IN']: | 
 |             sites[site]['IN_ONLY'][(iostandard, enum)] -= sites[site]['IN'][( | 
 |                 iostandard, enum)] | 
 |  | 
 |     common_bits = {} | 
 |     for site, iostd_type in sites: | 
 |         for group in sites[(site, iostd_type)]: | 
 |             if (site, group) not in common_bits: | 
 |                 common_bits[(site, group)] = set() | 
 |  | 
 |             for bits in sites[(site, iostd_type)][group].values(): | 
 |                 common_bits[(site, group)] |= bits | 
 |  | 
 |     slew_in_drives = {} | 
 |  | 
 |     for site, iostd_type in sites: | 
 |         common_bits[(site, 'IN')] |= common_bits[(site, 'IN_DIFF')] | 
 |         common_bits[(site, 'IN_DIFF')] |= common_bits[(site, 'IN')] | 
 |  | 
 |         # Only DIFF IOSTANDARDS such as LVDS or TMDS do not have DRIVE or SLEW features | 
 |         if iostd_type == "NORMAL": | 
 |             key = (site, iostd_type) | 
 |             common_bits[(site, 'DRIVE')] -= common_bits[(site, 'SLEW')] | 
 |             common_bits[(site, 'IN_ONLY')] |= common_bits[(site, 'DRIVE')] | 
 |  | 
 |             for iostandard, enum in sites[key]['DRIVE']: | 
 |                 slew_in_drive = common_bits[ | 
 |                     (site, 'SLEW')] & sites[key]['DRIVE'][(iostandard, enum)] | 
 |                 if slew_in_drive: | 
 |                     if (key, iostandard) not in slew_in_drives: | 
 |                         slew_in_drives[(key, iostandard)] = set() | 
 |  | 
 |                     slew_in_drives[(key, iostandard)] |= slew_in_drive | 
 |                     sites[key]['DRIVE'][(iostandard, enum)] -= slew_in_drive | 
 |  | 
 |     for site, iostandard in slew_in_drives: | 
 |         for _, enum in sites[site]['SLEW']: | 
 |             sites[site]['SLEW'][(iostandard, | 
 |                                  enum)] |= slew_in_drives[(site, iostandard)] | 
 |  | 
 |     for site in sites: | 
 |         for iostandard, enum in sites[site]['DRIVE']: | 
 |             sites[site]['DRIVE'][(iostandard, enum)] |= sites[site]['IN_USE'][( | 
 |                 iostandard, None)] | 
 |  | 
 |         for iostandard, enum in sites[site]['IN']: | 
 |             _, iostd_type = site | 
 |             if iostd_type == "ONLY_DIFF": | 
 |                 sites[site]['IN_DIFF'][(iostandard, enum)] = \ | 
 |                         sites[site]['IN'][(iostandard, enum)] | 
 |             elif sites[site]['IN_DIFF'][(iostandard, enum)]: | 
 |                 sites[site]['IN_DIFF'][(iostandard, enum)] |= \ | 
 |                         sites[site]['IN'][(iostandard, enum)] | 
 |  | 
 |     for site, iostd_type in sites: | 
 |         if iostd_type == "NORMAL": | 
 |             del sites[(site, iostd_type)]['OUT'] | 
 |  | 
 |     allow_zero = ['SLEW'] | 
 |  | 
 |     common_groups = dict() | 
 |     for site, iostd_type in sites: | 
 |         if site not in common_groups: | 
 |             common_groups[site] = dict() | 
 |  | 
 |         key = (site, iostd_type) | 
 |         for group in sites[key]: | 
 |             if iostd_type == "ONLY_DIFF" and group == "IN": | 
 |                 continue | 
 |  | 
 |             # Merge features that are identical. | 
 |             # | 
 |             # For example: | 
 |             # | 
 |             #  IOB18.IOB_Y1.LVCMOS15.IN 38_42 39_41 | 
 |             #  IOB18.IOB_Y1.LVCMOS18.IN 38_42 39_41 | 
 |             # | 
 |             # Must be grouped. | 
 |             for (iostandard, enum), bits in sites[key][group].items(): | 
 |                 if (bits, group) not in common_groups[site]: | 
 |                     common_groups[site][(bits, group)] = { | 
 |                         'IOSTANDARDS': set(), | 
 |                         'enums': set(), | 
 |                     } | 
 |  | 
 |                 common_groups[site][(bits, | 
 |                                      group)]['IOSTANDARDS'].add(iostandard) | 
 |                 if enum is not None: | 
 |                     common_groups[site][(bits, group)]['enums'].add(enum) | 
 |  | 
 |     visited_iostandards = list() | 
 |     for site, groups in common_groups.items(): | 
 |         for (bits, group), v in groups.items(): | 
 |             iostandards = v['IOSTANDARDS'] | 
 |             enums = v['enums'] | 
 |  | 
 |             # It happens that some features appear only in one of the IOB sites and not | 
 |             # in the other. This makes it hard to assign the correct features to the correct | 
 |             # site in the P&R toolchain. | 
 |             # | 
 |             # The following code makes sure that the same set of iostandards | 
 |             # (even if not really present at a site location) appears for each site | 
 |             for visited_iostandard, visited_group, visited_enums in visited_iostandards: | 
 |                 same_enum = enums == visited_enums | 
 |                 same_group = group == visited_group | 
 |                 compatible_iostd = any( | 
 |                     x in iostandards for x in visited_iostandard) | 
 |                 take_visited_iostd = len(visited_iostandard) > len(iostandards) | 
 |                 if same_enum and same_group and compatible_iostd and take_visited_iostd: | 
 |                     iostandards = visited_iostandard | 
 |                     break | 
 |  | 
 |             visited_iostandards.append((iostandards, group, enums)) | 
 |  | 
 |             iostandards_string = '_'.join(sorted(iostandards)) | 
 |  | 
 |             if enums: | 
 |                 feature = 'IOB18.{site}.{iostandards}.{group}.{enums}'.format( | 
 |                     site=site, | 
 |                     iostandards=iostandards_string, | 
 |                     group=group, | 
 |                     enums='_'.join(sorted(enums)), | 
 |                 ) | 
 |             else: | 
 |                 feature = 'IOB18.{site}.{iostandards}.{group}'.format( | 
 |                     site=site, | 
 |                     iostandards=iostandards_string, | 
 |                     group=group, | 
 |                 ) | 
 |  | 
 |             if not bits and group not in allow_zero: | 
 |                 continue | 
 |  | 
 |             neg_bits = frozenset( | 
 |                 '!{}'.format(b) for b in (common_bits[(site, group)] - bits)) | 
 |             neg_bits = filter_negbits(site, feature, neg_bits) | 
 |             print('{} {}'.format(feature, ' '.join(sorted(bits | neg_bits)))) | 
 |  | 
 |  | 
 | def main(): | 
 |     parser = argparse.ArgumentParser( | 
 |         description="Convert IOB rdb into good rdb." | 
 |         "") | 
 |     parser.add_argument('input_rdb') | 
 |  | 
 |     args = parser.parse_args() | 
 |  | 
 |     iostandard_lines = { | 
 |         "NORMAL": list(), | 
 |         "ONLY_DIFF": list(), | 
 |     } | 
 |  | 
 |     with open(args.input_rdb) as f: | 
 |         for l in f: | 
 |             if ('.SSTL' in l or '.LVCMOS' in l | 
 |                     or '.LVTTL' in l) and 'IOB_' in l: | 
 |                 iostandard_lines["NORMAL"].append(l) | 
 |             elif ('.TMDS' in l or 'LVDS' in l): | 
 |                 iostandard_lines["ONLY_DIFF"].append(l) | 
 |             else: | 
 |                 print(l.strip()) | 
 |  | 
 |     process_features_sets(iostandard_lines) | 
 |  | 
 |  | 
 | if __name__ == "__main__": | 
 |     main() |