| #!/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 | |
| import itertools | |
| import os | |
| import re | |
| from prjxray import util | |
| clb_int_zero_db = [ | |
| # CLB interconnet | |
| # Ex: | |
| # segbits_hclk_l.db:73:HCLK_L.HCLK_LEAF_CLK_B_BOTL4.HCLK_CK_BUFHCLK10 00_21 04_21 | |
| # segbits_int_l.db:207:INT_L.CLK_L0.GCLK_L_B8_WEST !01_21 00_21 00_25 01_20 01_24 | |
| "00_21 00_22 00_26 01_28|00_25 01_20 01_21 01_24", | |
| "00_23 00_30 01_22 01_25|00_27 00_29 01_26 01_29", | |
| "01_12 01_14 01_16 01_18|00_10 00_11 01_09 01_10", | |
| "00_13 01_17 00_15 00_17|00_18 00_19 01_13 00_14", | |
| "00_34 00_38 01_33 01_37|00_35 00_39 01_38 01_40", | |
| "00_33 00_41 01_32 01_34|00_37 00_42 01_36 01_41", | |
| ] | |
| def zero_range(tag, bits, wordmin, wordmax): | |
| """ | |
| If any bits occur wordmin <= word <= wordmax, | |
| default bits in wordmin <= word <= wordmax to 0 | |
| """ | |
| # The bit index, if any, that needs to be one hotted | |
| bitidx = None | |
| for bit in bits: | |
| if bit[0] == "!": | |
| continue | |
| fidx, bidx = [int(s) for s in bit.split("_")] | |
| if wordmin <= fidx <= wordmax: | |
| if bitidx is not None and bidx != bitidx: | |
| print("Old bit index: %u, new: %u" % (bitidx, bidx)) | |
| print("%s bits: %s" % (tag, str(bits))) | |
| raise ValueError("%s: inconsistent bit index" % tag) | |
| bitidx = bidx | |
| if bitidx is None: | |
| return | |
| for fidx in range(wordmin, wordmax + 1): | |
| bit = "%02d_%02d" % (fidx, bitidx) | |
| # Preserve 1 bits, set others to 0 | |
| if bit not in bits: | |
| bits.add("!" + bit) | |
| def bits_str(bits): | |
| """Convert a set into canonical form""" | |
| return ' '.join(sorted(list(bits))) | |
| class ZeroGroups(object): | |
| def __init__(self, zero_db): | |
| self.groups = [] | |
| self.bit_to_group = {} | |
| self.tag_to_groups = {} | |
| self.zero_tag_to_group = {} | |
| self.parse_zero_db(zero_db) | |
| def print_groups(self): | |
| print('Zero groups:') | |
| for bits in self.groups: | |
| print(bits_str(bits)) | |
| print('Zero tags:') | |
| for tag in self.zero_tag_to_group: | |
| print(tag, bits_str(self.zero_tag_to_group[tag])) | |
| def parse_zero_db(self, zero_db): | |
| """ Convert zero db format into data structure | |
| Zero db format examples: | |
| Ex: 01_02 04_05 | |
| Means find a line that has either of these bits | |
| If either of them occurs, default bits in that set to zero | |
| Ex: 01_02 04_05|07_08 10_11 | |
| If any bits from the first group occur, | |
| default bits in the second group to zero | |
| Ex: 01_02 04_05,ALL_ZERO | |
| ALL_ZERO is an enum that is part of the group but is all 0 | |
| It must have 0 candidates | |
| Ex: CLB.SLICE_X0.CLKINV ^ CLB.SLICE_X0.NOCLKINV | |
| CLB.SLICE_X0.NOCLKINV is all bits in CLB.SLICE_X0.CLKINV unset | |
| Ex: A | B ^ C | |
| C is all bits in (A)|(B) unset | |
| """ | |
| for zdb in zero_db: | |
| if "^" in zdb: | |
| self.groups.append(set()) | |
| zero_group = self.groups[-1] | |
| other_tags, allzero_tag = zdb.split('^') | |
| allzero_tag = allzero_tag.strip() | |
| for tag in other_tags.split(): | |
| self.tag_to_groups[tag.strip()] = [zero_group] | |
| self.zero_tag_to_group[allzero_tag] = zero_group | |
| continue | |
| allzero_tag = None | |
| if "," in zdb: | |
| zdb, allzero_tag = zdb.split(",") | |
| if "|" in zdb: | |
| a, b = zdb.split("|") | |
| a = a.split() | |
| b = b.split() | |
| self.groups.append(set(b)) | |
| zero_group = self.groups[-1] | |
| else: | |
| a = zdb.split() | |
| self.groups.append(set(a)) | |
| zero_group = self.groups[-1] | |
| if allzero_tag is not None: | |
| self.zero_tag_to_group[allzero_tag] = zero_group | |
| for bit in a: | |
| self.bit_to_group[bit] = zero_group | |
| def add_tag_bits(self, tag, bits): | |
| if tag in self.zero_tag_to_group: | |
| return | |
| group_ids = set() | |
| groups = [] | |
| if tag in self.tag_to_groups: | |
| assert len(self.tag_to_groups[tag]) == 1 | |
| self.tag_to_groups[tag][0] |= bits | |
| for bit in bits: | |
| if bit in self.bit_to_group: | |
| # Make sure each bit only belongs to one group | |
| assert id(self.bit_to_group[bit]) == id( | |
| self.tag_to_groups[tag]) | |
| else: | |
| self.bit_to_group[bit] = self.tag_to_groups[tag] | |
| group_ids.add(id(self.tag_to_groups[tag])) | |
| groups = self.tag_to_groups[tag] | |
| for bit in bits: | |
| if bit in self.bit_to_group: | |
| if id(self.bit_to_group[bit]) not in group_ids: | |
| group_ids.add(id(self.bit_to_group[bit])) | |
| groups.append(self.bit_to_group[bit]) | |
| self.tag_to_groups[tag] = groups | |
| def add_bits_from_zero_groups(self, tag, bits, strict=True, verbose=False): | |
| """ Add bits from a zero group, if needed | |
| Arguments | |
| --------- | |
| tag : str | |
| Tag being to examine for zero group | |
| bits : set of str | |
| Set of bits set on this tag | |
| strict : bool | |
| Assert that the size of the given group is the size of the given | |
| mask. | |
| verbose : bool | |
| Print to stdout grouping being made | |
| """ | |
| tag_is_masked = tag in self.tag_to_groups | |
| tag_is_zero = tag in self.zero_tag_to_group | |
| # Should not have a tag that is both masked and a zero tag. | |
| assert not (tag_is_masked and tag_is_zero) | |
| if tag_is_masked: | |
| for b in self.tag_to_groups[tag]: | |
| bits_orig = set(bits) | |
| for bit in b: | |
| if bit not in bits: | |
| bits.add("!" + bit) | |
| verbose and print( | |
| "Grouped %s: %s => %s" % | |
| (tag, bits_str(bits_orig), bits_str(bits))) | |
| if tag_is_zero: | |
| for bit in self.zero_tag_to_group[tag]: | |
| bits.add("!" + bit) | |
| def read_segbits(fn_in): | |
| """ | |
| Reads a segbits file. Removes duplcated lines. Returns a list of the lines. | |
| """ | |
| lines = [] | |
| llast = None | |
| with util.OpenSafeFile(fn_in, "r") as f: | |
| for line in f: | |
| # Hack: skip duplicate lines | |
| # This happens while merging a new multibit entry | |
| line = line.strip() | |
| if len(line) == 0: | |
| continue | |
| if line == llast: | |
| continue | |
| lines.append(line) | |
| return lines | |
| def add_zero_bits( | |
| fn_in, lines, zero_db, clb_int=False, strict=True, verbose=False): | |
| ''' | |
| Add multibit entries | |
| This requires adding some zero bits (ex: !31_09) | |
| If an entry has any of the | |
| ''' | |
| zero_groups = ZeroGroups(zero_db) | |
| new_lines = set() | |
| changes = 0 | |
| drops = 0 | |
| for line in lines: | |
| tag, bits, mode, _ = util.parse_db_line(line) | |
| if bits is not None and mode is None: | |
| zero_groups.add_tag_bits(tag, bits) | |
| if verbose: | |
| zero_groups.print_groups() | |
| for line in lines: | |
| tag, bits, mode, _ = util.parse_db_line(line) | |
| # an enum that needs masking | |
| # check below asserts that a mask was actually applied | |
| if mode and mode != "<0 candidates>" and not strict: | |
| verbose and print("WARNING: dropping unresolved line: %s" % line) | |
| drops += 1 | |
| continue | |
| assert mode not in ( | |
| "<const0>", | |
| "<const1>"), "Entries must be resolved. line: %s" % (line, ) | |
| if mode == "always": | |
| new_line = line | |
| else: | |
| if mode: | |
| assert mode == "<0 candidates>", line | |
| bits = set() | |
| else: | |
| bits = set(bits) | |
| """ | |
| This appears to be a large range of one hot interconnect bits | |
| They are immediately before the first CLB real bits | |
| """ | |
| if clb_int: | |
| zero_range(tag, bits, 22, 25) | |
| set_bits = [bit for bit in bits if bit[0] != '!'] | |
| if len(set_bits) not in [2, 4]: | |
| # All INT bits appear to be only have 2 or 4 bits. | |
| verbose and print( | |
| "WARNING: dropping line with %d bits, not [2, 4]: %s, %s" | |
| % (len(set_bits), bits, line)) | |
| drops += 1 | |
| continue | |
| zero_groups.add_bits_from_zero_groups( | |
| tag, bits, strict=strict, verbose=verbose) | |
| if strict: | |
| assert len(bits) > 0, 'Line {} found no bits.'.format(line) | |
| elif len(bits) == 0: | |
| verbose and print( | |
| "WARNING: dropping unresolved line: %s" % line) | |
| drops += 1 | |
| continue | |
| new_line = " ".join([tag] + sorted(bits)) | |
| if re.match(r'.*<.*>.*', new_line): | |
| print("Original line: %s" % line) | |
| assert 0, "Failed to remove line mode: %s" % (new_line) | |
| if new_line != line: | |
| changes += 1 | |
| new_lines.add(new_line) | |
| if drops: | |
| print("WARNING: %s dropped %s unresolved lines" % (fn_in, drops)) | |
| return changes, new_lines | |
| def update_mask(db_root, mask_db, src_dbs, offset=0): | |
| bits = set() | |
| mask_db_file = "%s/mask_%s.db" % (db_root, mask_db) | |
| if os.path.exists(mask_db_file): | |
| with util.OpenSafeFile(mask_db_file, "r") as f: | |
| for line in f: | |
| line = line.split() | |
| assert len(line) == 2 | |
| assert line[0] == "bit" | |
| bits.add(line[1]) | |
| for src_db in src_dbs: | |
| seg_db_file = "%s/segbits_%s.db" % (db_root, src_db) | |
| if not os.path.exists(seg_db_file): | |
| continue | |
| with util.OpenSafeFile(seg_db_file, "r") as f: | |
| for line in f: | |
| line = line.split() | |
| for bit in line[1:]: | |
| if bit[0] == "!": | |
| continue | |
| if offset != 0: | |
| m = re.match(r"(\d+)_(\d+)", bit) | |
| bit = "%02d_%02d" % ( | |
| int(m.group(1)), int(m.group(2)) + offset) | |
| bits.add(bit) | |
| if len(bits) > 0: | |
| with util.OpenSafeFile(mask_db_file, "w") as f: | |
| for bit in sorted(bits): | |
| print("bit %s" % bit, file=f) | |
| def load_zero_db(fn): | |
| # Remove comments and convert to list of lines | |
| ret = [] | |
| with util.OpenSafeFile(fn, "r") as f: | |
| for l in f: | |
| pos = l.find("#") | |
| if pos >= 0: | |
| l = l[0:pos] | |
| l = l.strip() | |
| if not l: | |
| continue | |
| ret.append(l) | |
| return ret | |
| def remove_ambiguous_solutions(fn_in, db_lines, strict=True, verbose=True): | |
| """ Removes features with identical solutions. | |
| During solving, some tags may be tightly coupled and solve to the same | |
| solution. In these cases, those solutions must be dropped until | |
| disambiguating information can be found. | |
| """ | |
| solutions = {} | |
| dropped_solutions = set() | |
| for l in db_lines: | |
| parts = l.split() | |
| feature = parts[0] | |
| bits = frozenset(parts[1:]) | |
| if bits in solutions: | |
| if strict: | |
| assert False, "Found solution {} at least twice, in {} and {}".format( | |
| bits, feature, solutions[bits]) | |
| else: | |
| dropped_solutions.add(bits) | |
| else: | |
| solutions[bits] = feature | |
| if strict: | |
| return 0, db_lines | |
| drops = 0 | |
| output_lines = set() | |
| for l in db_lines: | |
| parts = l.split() | |
| feature = parts[0] | |
| bits = frozenset(parts[1:]) | |
| if bits not in dropped_solutions: | |
| output_lines.add(l) | |
| drops += 1 | |
| else: | |
| if verbose: | |
| print( | |
| "WARNING: dropping line due to duplicate solution: %s" % l) | |
| if drops > 0: | |
| print("WARNING: %s dropped %s duplicate solutions" % (fn_in, drops)) | |
| return drops, output_lines | |
| def format_bits(tag, bits): | |
| """ Format tag and bits into line. """ | |
| bit_strs = [] | |
| for bit in sorted(list(bits), key=lambda b: b[1]): | |
| s = "!" if not bit[0] else "" | |
| s += "{:02d}_{:02d}".format(bit[1][0], bit[1][1]) | |
| bit_strs.append(s) | |
| return " ".join([tag] + bit_strs) | |
| def group_tags(lines, tag_groups, bit_groups): | |
| """ | |
| Implements tag grouping. If a tag belongs to a group then the common bits | |
| of that group are added to is as zeros. | |
| >>> tg = [{"A", "B"}] | |
| >>> bg = [{(1, 2), (3, 4)}] | |
| >>> res = group_tags({"A 1_2", "B 3_4"}, tg, bg) | |
| >>> (res[0], sorted(list(res[1]))) | |
| (2, ['A 01_02 !03_04', 'B !01_02 03_04']) | |
| >>> tg = [{"A", "B"}] | |
| >>> bg = [{(1, 2), (3, 4)}] | |
| >>> res = group_tags({"A 1_2", "B 3_4", "C 1_2"}, tg, bg) | |
| >>> (res[0], sorted(list(res[1]))) | |
| (2, ['A 01_02 !03_04', 'B !01_02 03_04', 'C 01_02']) | |
| """ | |
| changes = 0 | |
| new_lines = set() | |
| # Process lines | |
| for line in lines: | |
| line = line.strip() | |
| if not len(line): | |
| continue | |
| # Parse the line | |
| tag, bits, mode, _ = util.parse_db_line(line) | |
| if not bits: | |
| bits = set() | |
| else: | |
| bits = set([util.parse_tagbit(b) for b in bits]) | |
| # Check if the tag belongs to a group | |
| for tag_group, bit_group in zip(tag_groups, bit_groups): | |
| if tag in tag_group: | |
| # Add zero bits to the tag if not already there | |
| bit_coords = set([b[1] for b in bits]) | |
| for zero_bit in bit_group: | |
| if zero_bit not in bit_coords: | |
| bits.add((False, zero_bit)) | |
| # Format the line | |
| new_line = format_bits(tag, bits) | |
| # Add the line | |
| new_lines.add(new_line) | |
| changes += 1 | |
| break | |
| # It does not, pass it through unchanged | |
| else: | |
| new_lines.add(format_bits(tag, bits)) | |
| return changes, new_lines | |
| def update_seg_fns( | |
| fn_inouts, | |
| zero_db, | |
| tag_groups, | |
| clb_int, | |
| lazy=False, | |
| strict=True, | |
| verbose=False): | |
| seg_files = 0 | |
| seg_lines = 0 | |
| for fn_in, fn_out in fn_inouts: | |
| verbose and print("zb %s: %s" % (fn_in, os.path.exists(fn_in))) | |
| if lazy and not os.path.exists(fn_in): | |
| continue | |
| lines = read_segbits(fn_in) | |
| changes = 0 | |
| # Find common bits for tag groups | |
| bit_groups = find_common_bits_for_tag_groups(lines, tag_groups) | |
| # Group tags | |
| new_changes, lines = group_tags(lines, tag_groups, bit_groups) | |
| changes += new_changes | |
| new_changes, lines = add_zero_bits( | |
| fn_in, | |
| lines, | |
| zero_db, | |
| clb_int=clb_int, | |
| strict=strict, | |
| verbose=verbose) | |
| changes += new_changes | |
| new_changes, lines = remove_ambiguous_solutions( | |
| fn_in, | |
| lines, | |
| strict=strict, | |
| verbose=verbose, | |
| ) | |
| changes += new_changes | |
| with util.OpenSafeFile(fn_out, "w") as f: | |
| for line in sorted(lines): | |
| print(line, file=f) | |
| if changes is not None: | |
| seg_files += 1 | |
| seg_lines += changes | |
| print( | |
| "Segbit: checked %u files w/ %u changed lines" % | |
| (seg_files, seg_lines)) | |
| def update_masks(db_root): | |
| for mask_db, src_dbs in [ | |
| ("clbll_l", ("clbll_l", "int_l")), | |
| ("clbll_r", ("clbll_r", "int_r")), | |
| ("clblm_l", ("clblm_l", "int_l")), | |
| ("clblm_r", ("clblm_r", "int_r")), | |
| ("hclk_l", ("hclk_l", )), | |
| ("hclk_r", ("hclk_r", )), | |
| ("bram_l", ("bram_l", )), | |
| ("bram_r", ("bram_r", )), | |
| ("dsp_l", ("dsp_l", )), | |
| ("dsp_r", ("dsp_r", )), | |
| ]: | |
| update_mask(db_root, mask_db, src_dbs) | |
| for mask_db, src_dbs in [ | |
| ("bram_l", ("int_l", )), | |
| ("bram_r", ("int_r", )), | |
| ("dsp_l", ("int_l", )), | |
| ("dsp_r", ("int_r", )), | |
| ]: | |
| for k in range(5): | |
| update_mask(db_root, mask_db, src_dbs, offset=64 * k) | |
| print("Mask: checked files") | |
| def update_segs( | |
| db_root, | |
| clb_int, | |
| seg_fn_in, | |
| seg_fn_out, | |
| zero_db_fn, | |
| tag_groups, | |
| strict=True, | |
| verbose=False): | |
| if clb_int: | |
| zero_db = clb_int_zero_db | |
| lazy = True | |
| def gen_fns(): | |
| for tile_type in ["int_l", "int_r", "clbll_l", "clbll_r", | |
| "clblm_l", "clblm_r"]: | |
| fn = "%s/segbits_%s.db" % (db_root, tile_type) | |
| yield (fn, fn) | |
| fn_inouts = list(gen_fns()) | |
| else: | |
| assert seg_fn_in | |
| assert zero_db_fn | |
| lazy = False | |
| if not seg_fn_out: | |
| seg_fn_out = seg_fn_in | |
| fn_inouts = [(seg_fn_in, seg_fn_out)] | |
| zero_db = load_zero_db(zero_db_fn) | |
| print("CLB INT mode: %s" % clb_int) | |
| print("Segbit groups: %s" % len(zero_db)) | |
| update_seg_fns( | |
| fn_inouts, | |
| zero_db, | |
| tag_groups, | |
| clb_int, | |
| lazy=lazy, | |
| strict=strict, | |
| verbose=verbose) | |
| def find_common_bits_for_tag_groups(lines, tag_groups): | |
| """ | |
| For each tag group finds a common set of bits that have value of one. | |
| """ | |
| bit_groups = [] | |
| for tag_group in tag_groups: | |
| bit_group = set() | |
| for line in lines: | |
| tag, bits, mode, _ = util.parse_db_line(line) | |
| if not bits: | |
| continue | |
| bits = set([util.parse_tagbit(b) for b in bits]) | |
| if tag in tag_group and len(bits): | |
| ones = set([b[1] for b in bits if b[0]]) | |
| bit_group |= ones | |
| bit_groups.append(bit_group) | |
| return bit_groups | |
| def load_tag_groups(file_name): | |
| """ | |
| Loads tag groups from a text file. | |
| A tag group is defined by specifying a space separated list of tags within | |
| a single line. Lines that are empty or start with '#' are ignored. | |
| """ | |
| tag_groups = [] | |
| # Load tag group specifications | |
| with util.OpenSafeFile(file_name, "r") as fp: | |
| for line in fp: | |
| line = line.strip() | |
| if len(line) == 0 or line.startswith("#"): | |
| continue | |
| group = set(line.split()) | |
| if len(group): | |
| tag_groups.append(group) | |
| # Check if all tag groups are exclusive | |
| for tag_group_a, tag_group_b in itertools.combinations(tag_groups, 2): | |
| tags = tag_group_a & tag_group_b | |
| if len(tags): | |
| raise RuntimeError( | |
| "Tag(s) {} are present in multiple groups".format( | |
| " ".join(tags))) | |
| return tag_groups | |
| def run( | |
| db_root, | |
| clb_int=False, | |
| zero_db_fn=None, | |
| seg_fn_in=None, | |
| seg_fn_out=None, | |
| groups_fn_in=None, | |
| strict=None, | |
| verbose=False): | |
| if strict is None: | |
| strict = not clb_int | |
| # Load tag groups | |
| tag_groups = [] | |
| if groups_fn_in is not None: | |
| tag_groups = load_tag_groups(groups_fn_in) | |
| # Probably should split this into two programs | |
| update_segs( | |
| db_root, | |
| clb_int=clb_int, | |
| seg_fn_in=seg_fn_in, | |
| seg_fn_out=seg_fn_out, | |
| zero_db_fn=zero_db_fn, | |
| tag_groups=tag_groups, | |
| strict=strict, | |
| verbose=verbose) | |
| if clb_int: | |
| update_masks(db_root) | |
| def main(): | |
| import argparse | |
| parser = argparse.ArgumentParser(description='Create multi-bit entries') | |
| util.db_root_arg(parser) | |
| parser.add_argument('--verbose', action='store_true', help='') | |
| parser.add_argument( | |
| '--clb-int', action='store_true', help='Fixup CLB interconnect') | |
| parser.add_argument('--zero-db', help='Apply custom patches') | |
| parser.add_argument('--seg-fn-in', help='') | |
| parser.add_argument('--seg-fn-out', help='') | |
| util.add_bool_arg(parser, "--strict", default=False) | |
| parser.add_argument( | |
| "-g", | |
| "--groups", | |
| type=str, | |
| default=None, | |
| help="Input tag group definition file") | |
| args = parser.parse_args() | |
| run( | |
| args.db_root, | |
| args.clb_int, | |
| args.zero_db, | |
| args.seg_fn_in, | |
| args.seg_fn_out, | |
| args.groups, | |
| strict=args.strict, | |
| verbose=args.verbose) | |
| if __name__ == '__main__': | |
| main() |