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