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