|  | #!/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() |