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