diff options
| author | Clifford Wolf <clifford@clifford.at> | 2019-06-09 10:31:09 +0200 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-06-09 10:31:09 +0200 | 
| commit | 0f5feeaae9d581ea47eaf0d8ed0512962d1e85c0 (patch) | |
| tree | 28f41bbbbde97e19f6f27445da6e487b2b33f2d2 | |
| parent | 625105c0d136e05b965c9192f6a3cc249f5cd9ae (diff) | |
| parent | eec6555603d6f77ebec556ff3bf55ef0194381eb (diff) | |
| download | icestorm-0f5feeaae9d581ea47eaf0d8ed0512962d1e85c0.tar.gz icestorm-0f5feeaae9d581ea47eaf0d8ed0512962d1e85c0.tar.bz2 icestorm-0f5feeaae9d581ea47eaf0d8ed0512962d1e85c0.zip | |
Merge pull request #221 from mbuesch/icebox-lru-cache
Reduce icebox build time
| -rw-r--r-- | icebox/icebox.py | 156 | ||||
| -rwxr-xr-x | icebox/icebox_asc2hlc.py | 37 | ||||
| -rwxr-xr-x | icebox/icebox_colbuf.py | 5 | ||||
| -rwxr-xr-x | icebox/icebox_diff.py | 3 | ||||
| -rwxr-xr-x | icebox/icebox_explain.py | 7 | ||||
| -rwxr-xr-x | icebox/icebox_hlc2asc.py | 31 | ||||
| -rwxr-xr-x | icebox/icebox_html.py | 11 | ||||
| -rwxr-xr-x | icebox/icebox_maps.py | 5 | ||||
| -rwxr-xr-x | icebox/icebox_stat.py | 13 | ||||
| -rwxr-xr-x | icebox/icebox_vlog.py | 41 | 
10 files changed, 173 insertions, 136 deletions
| diff --git a/icebox/icebox.py b/icebox/icebox.py index a6852fc..c4b435c 100644 --- a/icebox/icebox.py +++ b/icebox/icebox.py @@ -16,7 +16,33 @@  #  import iceboxdb -import re, sys +import re, sys, functools + + +if True: +    # icebox uses lots of regular expressions. +    # Supply cached versions of common re functions +    # to avoid re-calculating regular expression results +    # over and over again. +    re_cache_sizes = 2**14 + +    @functools.lru_cache(maxsize=re_cache_sizes) +    def re_match_cached(*args): +        return re.match(*args) + +    @functools.lru_cache(maxsize=re_cache_sizes) +    def re_sub_cached(*args): +        return re.sub(*args) + +    @functools.lru_cache(maxsize=re_cache_sizes) +    def re_search_cached(*args): +        return re.search(*args) +else: +    # Disable regular expression caching. +    re_match_cached = re.match +    re_sub_cached = re.sub +    re_search_cached = re.search +  class iceconfig:      def __init__(self): @@ -36,6 +62,7 @@ class iceconfig:          self.ram_data = dict()          self.extra_bits = set()          self.symbols = dict() +        self.tile_has_net.cache_clear()      def setup_empty_384(self):          self.clear() @@ -456,6 +483,7 @@ class iceconfig:              return self.tile_has_net(x, y, entry[2]) and self.tile_has_net(x, y, entry[3])          return True +    @functools.lru_cache(maxsize=2**16)      def tile_has_net(self, x, y, netname):          if netname.startswith("logic_op_"):              if netname.startswith("logic_op_bot_"): @@ -576,11 +604,11 @@ class iceconfig:              for net in self.follow_funcnet(x, y, 3) | self.follow_funcnet(x, y, 7):                  if self.tile_pos(net[0], net[1]) == "x": funcnets.add(net) -        match = re.match(r"lutff_(\d+)/out", netname) +        match = re_match_cached(r"lutff_(\d+)/out", netname)          if match:              funcnets |= self.follow_funcnet(x, y, int(match.group(1))) -        match = re.match(r"ram/RDATA_(\d+)", netname) +        match = re_match_cached(r"ram/RDATA_(\d+)", netname)          if match:              if self.device == "1k":                  funcnets |= self.follow_funcnet(x, y, int(match.group(1)) % 8) @@ -594,7 +622,7 @@ class iceconfig:          return funcnets      def ultraplus_follow_corner(self, corner, direction, netname): -        m = re.match("span4_(horz|vert)_([lrtb])_(\d+)$", netname) +        m = re_match_cached("span4_(horz|vert)_([lrtb])_(\d+)$", netname)          if not m:              return None          cur_edge = m.group(2) @@ -676,12 +704,12 @@ class iceconfig:                      if self.tile_pos(nx, ny) is not None:                          neighbours.add((nx, ny, netname)) -        match = re.match(r"sp4_r_v_b_(\d+)", netname) +        match = re_match_cached(r"sp4_r_v_b_(\d+)", netname)          if match and ((0 < x < self.max_x-1) or (self.is_ultra() and (x < self.max_x))):              neighbours.add((x+1, y, sp4v_normalize("sp4_v_b_" + match.group(1))))          #print('\tafter r_v_b', neighbours) -        match = re.match(r"sp4_v_[bt]_(\d+)", netname) +        match = re_match_cached(r"sp4_v_[bt]_(\d+)", netname)          if match and (1 < x < self.max_x or (self.is_ultra() and (x > 0))):              n = sp4v_normalize(netname, "b")              if n is not None: @@ -689,7 +717,7 @@ class iceconfig:                  neighbours.add((x-1, y, n))          #print('\tafter v_[bt]', neighbours) -        match = re.match(r"(logic|neigh)_op_(...)_(\d+)", netname) +        match = re_match_cached(r"(logic|neigh)_op_(...)_(\d+)", netname)          if match:              if match.group(2) == "bot": nx, ny = (x,   y-1)              if match.group(2) == "bnl": nx, ny = (x-1, y-1) @@ -716,8 +744,8 @@ class iceconfig:                          s = self.ultraplus_follow_corner(self.get_corner(s[0], s[1]), direction, n)                          if s is None:                              continue -                    elif re.match("span4_(vert|horz)_[lrtb]_\d+$", n) and not self.is_ultra(): -                        m = re.match("span4_(vert|horz)_([lrtb])_\d+$", n) +                    elif re_match_cached("span4_(vert|horz)_[lrtb]_\d+$", n) and not self.is_ultra(): +                        m = re_match_cached("span4_(vert|horz)_([lrtb])_\d+$", n)                          vert_net = n.replace("_l_", "_t_").replace("_r_", "_b_").replace("_horz_", "_vert_")                          horz_net = n.replace("_t_", "_l_").replace("_b_", "_r_").replace("_vert_", "_horz_") @@ -976,7 +1004,7 @@ class iceconfig:                  if line[0] == ".ipcon_tile":                      self.ipcon_tiles[(int(line[1]), int(line[2]))] = current_data                      continue -                match = re.match(r".dsp(\d)_tile", line[0]) +                match = re_match_cached(r".dsp(\d)_tile", line[0])                  if match:                      self.dsp_tiles[int(match.group(1))][(int(line[1]), int(line[2]))] = current_data                      continue @@ -1069,7 +1097,7 @@ else:      valid_sp12_v_b = set(range(24))  def sp4h_normalize(netname, edge=""): -    m = re.match("sp4_h_([lr])_(\d+)$", netname) +    m = re_match_cached("sp4_h_([lr])_(\d+)$", netname)      assert m      if not m: return None      cur_edge = m.group(1) @@ -1092,7 +1120,7 @@ def sp4h_normalize(netname, edge=""):  # "Normalization" of span4 (not just sp4) is needed during Ultra/UltraPlus  # corner tracing  def ultra_span4_horz_normalize(netname, edge=""): -    m = re.match("span4_horz_([rl])_(\d+)$", netname) +    m = re_match_cached("span4_horz_([rl])_(\d+)$", netname)      assert m      if not m: return None      cur_edge = m.group(1) @@ -1118,7 +1146,7 @@ def ultra_span4_horz_normalize(netname, edge=""):      assert False  def sp4v_normalize(netname, edge=""): -    m = re.match("sp4_v_([bt])_(\d+)$", netname) +    m = re_match_cached("sp4_v_([bt])_(\d+)$", netname)      assert m      if not m: return None      cur_edge = m.group(1) @@ -1140,7 +1168,7 @@ def sp4v_normalize(netname, edge=""):      return netname  def sp12h_normalize(netname, edge=""): -    m = re.match("sp12_h_([lr])_(\d+)$", netname) +    m = re_match_cached("sp12_h_([lr])_(\d+)$", netname)      assert m      if not m: return None      cur_edge = m.group(1) @@ -1162,7 +1190,7 @@ def sp12h_normalize(netname, edge=""):      return netname  def sp12v_normalize(netname, edge=""): -    m = re.match("sp12_v_([bt])_(\d+)$", netname) +    m = re_match_cached("sp12_v_([bt])_(\d+)$", netname)      assert m      if not m: return None      cur_edge = m.group(1) @@ -1197,18 +1225,18 @@ def netname_normalize(netname, edge="", ramb=False, ramt=False, ramb_8k=False, r      netname = netname.replace("wire_con_box/", "")      netname = netname.replace("wire_bram/", "")      if (ramb or ramt or ramb_8k or ramt_8k) and netname.startswith("input"): -        match = re.match(r"input(\d)_(\d)", netname) +        match = re_match_cached(r"input(\d)_(\d)", netname)          idx1, idx2 = (int(match.group(1)), int(match.group(2)))          if ramb: netname="ram/WADDR_%d" % (idx1*4 + idx2)          if ramt: netname="ram/RADDR_%d" % (idx1*4 + idx2)          if ramb_8k: netname="ram/RADDR_%d" % ([7, 6, 5, 4, 3, 2, 1, 0, -1, -1, -1, -1, -1, 10, 9, 8][idx1*4 + idx2])          if ramt_8k: netname="ram/WADDR_%d" % ([7, 6, 5, 4, 3, 2, 1, 0, -1, -1, -1, -1, -1, 10, 9, 8][idx1*4 + idx2]) -    match = re.match(r"(...)_op_(.*)", netname) +    match = re_match_cached(r"(...)_op_(.*)", netname)      if match and (match.group(1) != "slf"):          netname = "neigh_op_%s_%s" % (match.group(1), match.group(2)) -    if re.match(r"lutff_7/(cen|clk|s_r)", netname): +    if re_match_cached(r"lutff_7/(cen|clk|s_r)", netname):          netname = netname.replace("lutff_7/", "lutff_global/") -    if re.match(r"io_1/(cen|inclk|outclk)", netname): +    if re_match_cached(r"io_1/(cen|inclk|outclk)", netname):          netname = netname.replace("io_1/", "io_global/")      if netname == "carry_in_mux/cout":          return "carry_in_mux" @@ -1216,113 +1244,113 @@ def netname_normalize(netname, edge="", ramb=False, ramt=False, ramb_8k=False, r  def pos_has_net(pos, netname):      if pos in ("l", "r"): -        if re.search(r"_vert_\d+$", netname): return False -        if re.search(r"_horz_[rl]_\d+$", netname): return False +        if re_search_cached(r"_vert_\d+$", netname): return False +        if re_search_cached(r"_horz_[rl]_\d+$", netname): return False      if pos in ("t", "b"): -        if re.search(r"_horz_\d+$", netname): return False -        if re.search(r"_vert_[bt]_\d+$", netname): return False +        if re_search_cached(r"_horz_\d+$", netname): return False +        if re_search_cached(r"_vert_[bt]_\d+$", netname): return False      return True  def pos_follow_net(pos, direction, netname, is_ultra):      if pos == "x" or ((pos in ("l", "r")) and is_ultra): -            m = re.match("sp4_h_[lr]_(\d+)$", netname) +            m = re_match_cached("sp4_h_[lr]_(\d+)$", netname)              if m and direction in ("l", "L"):                  n = sp4h_normalize(netname, "l")                  if n is not None:                      if direction == "l" or is_ultra: -                        n = re.sub("_l_", "_r_", n) +                        n = re_sub_cached("_l_", "_r_", n)                          n = sp4h_normalize(n)                      else: -                        n = re.sub("_l_", "_", n) -                        n = re.sub("sp4_h_", "span4_horz_", n) +                        n = re_sub_cached("_l_", "_", n) +                        n = re_sub_cached("sp4_h_", "span4_horz_", n)                      return n              if m and direction in ("r", "R"):                  n = sp4h_normalize(netname, "r")                  if n is not None:                      if direction == "r" or is_ultra: -                        n = re.sub("_r_", "_l_", n) +                        n = re_sub_cached("_r_", "_l_", n)                          n = sp4h_normalize(n)                      else: -                        n = re.sub("_r_", "_", n) -                        n = re.sub("sp4_h_", "span4_horz_", n) +                        n = re_sub_cached("_r_", "_", n) +                        n = re_sub_cached("sp4_h_", "span4_horz_", n)                      return n -            m = re.match("sp4_v_[tb]_(\d+)$", netname) +            m = re_match_cached("sp4_v_[tb]_(\d+)$", netname)              if m and direction in ("t", "T"):                  n = sp4v_normalize(netname, "t")                  if n is not None:                      if is_ultra and direction == "T" and pos in ("l", "r"): -                        return re.sub("sp4_v_", "span4_vert_", n) +                        return re_sub_cached("sp4_v_", "span4_vert_", n)                      elif direction == "t": -                        n = re.sub("_t_", "_b_", n) +                        n = re_sub_cached("_t_", "_b_", n)                          n = sp4v_normalize(n)                      else: -                        n = re.sub("_t_", "_", n) -                        n = re.sub("sp4_v_", "span4_vert_", n) +                        n = re_sub_cached("_t_", "_", n) +                        n = re_sub_cached("sp4_v_", "span4_vert_", n)                      return n              if m and direction in ("b", "B"):                  n = sp4v_normalize(netname, "b")                  if n is not None:                      if is_ultra and direction == "B" and pos in ("l", "r"): -                        return re.sub("sp4_v_", "span4_vert_", n) +                        return re_sub_cached("sp4_v_", "span4_vert_", n)                      elif direction == "b": -                        n = re.sub("_b_", "_t_", n) +                        n = re_sub_cached("_b_", "_t_", n)                          n = sp4v_normalize(n)                      else: -                        n = re.sub("_b_", "_", n) -                        n = re.sub("sp4_v_", "span4_vert_", n) +                        n = re_sub_cached("_b_", "_", n) +                        n = re_sub_cached("sp4_v_", "span4_vert_", n)                      return n -            m = re.match("sp12_h_[lr]_(\d+)$", netname) +            m = re_match_cached("sp12_h_[lr]_(\d+)$", netname)              if m and direction in ("l", "L"):                  n = sp12h_normalize(netname, "l")                  if n is not None:                      if direction == "l" or is_ultra: -                        n = re.sub("_l_", "_r_", n) +                        n = re_sub_cached("_l_", "_r_", n)                          n = sp12h_normalize(n)                      else: -                        n = re.sub("_l_", "_", n) -                        n = re.sub("sp12_h_", "span12_horz_", n) +                        n = re_sub_cached("_l_", "_", n) +                        n = re_sub_cached("sp12_h_", "span12_horz_", n)                      return n              if m and direction in ("r", "R"):                  n = sp12h_normalize(netname, "r")                  if n is not None:                      if direction == "r" or is_ultra: -                        n = re.sub("_r_", "_l_", n) +                        n = re_sub_cached("_r_", "_l_", n)                          n = sp12h_normalize(n)                      else: -                        n = re.sub("_r_", "_", n) -                        n = re.sub("sp12_h_", "span12_horz_", n) +                        n = re_sub_cached("_r_", "_", n) +                        n = re_sub_cached("sp12_h_", "span12_horz_", n)                      return n -            m = re.match("sp12_v_[tb]_(\d+)$", netname) +            m = re_match_cached("sp12_v_[tb]_(\d+)$", netname)              if m and direction in ("t", "T"):                  n = sp12v_normalize(netname, "t")                  if n is not None:                      if direction == "t": -                        n = re.sub("_t_", "_b_", n) +                        n = re_sub_cached("_t_", "_b_", n)                          n = sp12v_normalize(n)                      elif direction == "T" and pos in ("l", "r"):                          pass                      else: -                        n = re.sub("_t_", "_", n) -                        n = re.sub("sp12_v_", "span12_vert_", n) +                        n = re_sub_cached("_t_", "_", n) +                        n = re_sub_cached("sp12_v_", "span12_vert_", n)                      return n              if m and direction in ("b", "B"):                  n = sp12v_normalize(netname, "b")                  if n is not None:                      if direction == "b": -                        n = re.sub("_b_", "_t_", n) +                        n = re_sub_cached("_b_", "_t_", n)                          n = sp12v_normalize(n)                      elif direction == "B" and pos in ("l", "r"):                          pass                      else: -                        n = re.sub("_b_", "_", n) -                        n = re.sub("sp12_v_", "span12_vert_", n) +                        n = re_sub_cached("_b_", "_", n) +                        n = re_sub_cached("sp12_v_", "span12_vert_", n)                      return n      if (pos in ("l", "r" )) and (not is_ultra): -        m = re.match("span4_vert_([bt])_(\d+)$", netname) +        m = re_match_cached("span4_vert_([bt])_(\d+)$", netname)          if m:              case, idx = direction + m.group(1), int(m.group(2))              if case == "tt": @@ -1335,7 +1363,7 @@ def pos_follow_net(pos, direction, netname, is_ultra):                  return "span4_vert_t_%d" % idx      if pos in ("t", "b" ): -        m = re.match("span4_horz_([rl])_(\d+)$", netname) +        m = re_match_cached("span4_horz_([rl])_(\d+)$", netname)          if m:              case, idx = direction + m.group(1), int(m.group(2))              if direction == "L": @@ -1352,27 +1380,27 @@ def pos_follow_net(pos, direction, netname, is_ultra):                  return "span4_horz_l_%d" % idx      if pos == "l" and direction == "r" and (not is_ultra): -            m = re.match("span4_horz_(\d+)$", netname) +            m = re_match_cached("span4_horz_(\d+)$", netname)              if m: return sp4h_normalize("sp4_h_l_%s" % m.group(1)) -            m = re.match("span12_horz_(\d+)$", netname) +            m = re_match_cached("span12_horz_(\d+)$", netname)              if m: return sp12h_normalize("sp12_h_l_%s" % m.group(1))      if pos == "r" and direction == "l" and (not is_ultra): -            m = re.match("span4_horz_(\d+)$", netname) +            m = re_match_cached("span4_horz_(\d+)$", netname)              if m: return sp4h_normalize("sp4_h_r_%s" % m.group(1)) -            m = re.match("span12_horz_(\d+)$", netname) +            m = re_match_cached("span12_horz_(\d+)$", netname)              if m: return sp12h_normalize("sp12_h_r_%s" % m.group(1))      if pos == "t" and direction == "b": -            m = re.match("span4_vert_(\d+)$", netname) +            m = re_match_cached("span4_vert_(\d+)$", netname)              if m: return sp4v_normalize("sp4_v_t_%s" % m.group(1)) -            m = re.match("span12_vert_(\d+)$", netname) +            m = re_match_cached("span12_vert_(\d+)$", netname)              if m: return sp12v_normalize("sp12_v_t_%s" % m.group(1))      if pos == "b" and direction == "t": -            m = re.match("span4_vert_(\d+)$", netname) +            m = re_match_cached("span4_vert_(\d+)$", netname)              if m: return sp4v_normalize("sp4_v_b_%s" % m.group(1)) -            m = re.match("span12_vert_(\d+)$", netname) +            m = re_match_cached("span12_vert_(\d+)$", netname)              if m: return sp12v_normalize("sp12_v_b_%s" % m.group(1))      return None @@ -1405,7 +1433,7 @@ def get_negclk_bit(tile):      return tile[0][0]  def key_netname(netname): -    return re.sub(r"\d+", lambda m: "%09d" % int(m.group(0)), netname) +    return re_sub_cached(r"\d+", lambda m: "%09d" % int(m.group(0)), netname)  def run_checks_neigh():      print("Running consistency checks on neighbour finder..") diff --git a/icebox/icebox_asc2hlc.py b/icebox/icebox_asc2hlc.py index facca4b..003106f 100755 --- a/icebox/icebox_asc2hlc.py +++ b/icebox/icebox_asc2hlc.py @@ -15,6 +15,7 @@  import getopt, os, re, sys  import icebox +from icebox import re_match_cached  GLB_NETWK_EXTERNAL_BLOCKS = [(13, 8, 1), (0, 8, 1), (7, 17, 0), (7, 0, 0),                               (0, 9, 0), (13, 9, 0), (6, 0, 1), (6, 17, 1)] @@ -63,24 +64,24 @@ def translate_netname(x, y, fw, fh, net):      # logic and RAM tiles -    match = re.match(r'sp4_h_r_(\d+)$', net) +    match = re_match_cached(r'sp4_h_r_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 12)          return 'span4_y%d_g%d_%d' % (y, x - g + 4, i) -    match = re.match(r'sp4_h_l_(\d+)$', net) +    match = re_match_cached(r'sp4_h_l_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 12)          return 'span4_y%d_g%d_%d' % (y, x - g + 3, i) -    match = re.match(r'sp4_v_b_(\d+)$', net) +    match = re_match_cached(r'sp4_v_b_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 12)          return 'span4_x%d_g%d_%d' % (x, y + g, i) -    match = re.match(r'sp4_v_t_(\d+)$', net) +    match = re_match_cached(r'sp4_v_t_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 12)          return 'span4_x%d_g%d_%d' % (x, y + g + 1, i) -    match = re.match(r'sp4_r_v_b_(\d+)$', net) +    match = re_match_cached(r'sp4_r_v_b_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 12)          if x == fw: @@ -89,27 +90,27 @@ def translate_netname(x, y, fw, fh, net):          else:              return 'span4_x%d_g%d_%d' % (x + 1, y + g, i) -    match = re.match(r'sp12_h_r_(\d+)$', net) +    match = re_match_cached(r'sp12_h_r_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 2)          return 'span12_y%d_g%d_%d' % (y, x - g + 12, i) -    match = re.match(r'sp12_h_l_(\d+)$', net) +    match = re_match_cached(r'sp12_h_l_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 2)          return 'span12_y%d_g%d_%d' % (y, x - g + 11, i) -    match = re.match(r'sp12_v_b_(\d+)$', net) +    match = re_match_cached(r'sp12_v_b_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 2)          return 'span12_x%d_g%d_%d' % (x, y + g, i) -    match = re.match(r'sp12_v_t_(\d+)$', net) +    match = re_match_cached(r'sp12_v_t_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 2)          return 'span12_x%d_g%d_%d' % (x, y + g + 1, i)      # I/O tiles -    match = re.match(r'span4_horz_(\d+)$', net) +    match = re_match_cached(r'span4_horz_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 12)          if x == 0: @@ -117,7 +118,7 @@ def translate_netname(x, y, fw, fh, net):          else:              return 'span4_y%d_g%d_%d' % (y, x - g + 3, i) -    match = re.match(r'span4_vert_(\d+)$', net) +    match = re_match_cached(r'span4_vert_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 12)          if y == 0: @@ -125,7 +126,7 @@ def translate_netname(x, y, fw, fh, net):          else:              return 'span4_x%d_g%d_%d' % (x, y + g, i) -    match = re.match(r'span12_horz_(\d+)$', net) +    match = re_match_cached(r'span12_horz_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 2)          if x == 0: @@ -133,7 +134,7 @@ def translate_netname(x, y, fw, fh, net):          else:              return 'span12_y%d_g%d_%d' % (y, x - g + 11, i) -    match = re.match(r'span12_vert_(\d+)$', net) +    match = re_match_cached(r'span12_vert_(\d+)$', net)      if match is not None:          g, i = group_and_index(match.group(1), 2)          if y == 0: @@ -143,7 +144,7 @@ def translate_netname(x, y, fw, fh, net):      # I/O tiles - peripheral wires -    match = re.match(r'span4_horz_r_(\d+)$', net) +    match = re_match_cached(r'span4_horz_r_(\d+)$', net)      if match is not None:          n = int(match.group(1)); g = n // 4; i = n % 4          if y == 0: @@ -161,7 +162,7 @@ def translate_netname(x, y, fw, fh, net):              else:                  return 'span4_top_g%d_%d' % (x + 4 - g, i) -    match = re.match(r'span4_horz_l_(\d+)$', net) +    match = re_match_cached(r'span4_horz_l_(\d+)$', net)      if match is not None:          n = int(match.group(1)); g = n // 4; i = n % 4          if y == 0: @@ -175,7 +176,7 @@ def translate_netname(x, y, fw, fh, net):              else:                  return 'span4_top_g%d_%d' % (x + 3 - g, i) -    match = re.match(r'span4_vert_b_(\d+)$', net) +    match = re_match_cached(r'span4_vert_b_(\d+)$', net)      if match is not None:          n = int(match.group(1)); g = n // 4; i = n % 4          if x == 0: @@ -193,7 +194,7 @@ def translate_netname(x, y, fw, fh, net):              else:                  return 'span4_right_g%d_%d' % (y + g, i) -    match = re.match(r'span4_vert_t_(\d+)$', net) +    match = re_match_cached(r'span4_vert_t_(\d+)$', net)      if match is not None:          n = int(match.group(1)); g = n // 4; i = n % 4          if x == 0: @@ -740,7 +741,7 @@ class Tile:          for entry in db:              # LC bits don't have a useful entry in the database; skip them              # for now -            if re.match(r'LC_', entry[1]): +            if re_match_cached(r'LC_', entry[1]):                  continue              # some nets have different names depending on the tile; filter diff --git a/icebox/icebox_colbuf.py b/icebox/icebox_colbuf.py index 26b8940..ec6843e 100755 --- a/icebox/icebox_colbuf.py +++ b/icebox/icebox_colbuf.py @@ -16,6 +16,7 @@  #  import icebox +from icebox import re_match_cached  import getopt, sys, re  check_mode = False @@ -70,7 +71,7 @@ def make_cache(stmt, raw_db):                  if bit.startswith("!"):                      value = "0"                      bit = bit[1:] -                match = re.match("B([0-9]+)\[([0-9]+)\]", bit) +                match = re_match_cached("B([0-9]+)\[([0-9]+)\]", bit)                  cache_entry[1].append((int(match.group(1)), int(match.group(2)), value))              cache.append(cache_entry)      return cache @@ -120,7 +121,7 @@ def set_colbuf(ic, tile, bit, value):      tile_db = ic.tile_db(tile[0], tile[1])      for entry in tile_db:          if entry[1] == "ColBufCtrl" and entry[2] == "glb_netwk_%d" % bit: -            match = re.match("B([0-9]+)\[([0-9]+)\]", entry[0][0]) +            match = re_match_cached("B([0-9]+)\[([0-9]+)\]", entry[0][0])              l = tile_dat[int(match.group(1))]              n = int(match.group(2))              l = l[:n] + value + l[n+1:] diff --git a/icebox/icebox_diff.py b/icebox/icebox_diff.py index c0db200..5252fc4 100755 --- a/icebox/icebox_diff.py +++ b/icebox/icebox_diff.py @@ -16,6 +16,7 @@  #  import icebox +from icebox import re_match_cached  import sys  import re @@ -54,7 +55,7 @@ def explained_bits(db, tile):                  bits.add("!B%d[%d]" % (k, i))      text = set()      for entry in db: -        if re.match(r"LC_", entry[1]): +        if re_match_cached(r"LC_", entry[1]):              continue          if entry[1] in ("routing", "buffer"):              continue diff --git a/icebox/icebox_explain.py b/icebox/icebox_explain.py index 4e678ff..f843c09 100755 --- a/icebox/icebox_explain.py +++ b/icebox/icebox_explain.py @@ -16,6 +16,7 @@  #  import icebox +from icebox import re_match_cached, re_search_cached  import getopt, sys, re  print_bits = False @@ -82,14 +83,14 @@ def print_tile(stmt, ic, x, y, tile, db):              else:                  bits.add("!B%d[%d]" % (k, i)) -    if re.search(r"logic_tile", stmt): +    if re_search_cached(r"logic_tile", stmt):          active_luts = set([i for i in range(8) if "1" in icebox.get_lutff_bits(tile, i)])      text = set()      used_lc = set()      text_default_mask = 0      for entry in db: -        if re.match(r"LC_", entry[1]): +        if re_match_cached(r"LC_", entry[1]):              continue          if entry[1] in ("routing", "buffer"):              if not ic.tile_has_net(x, y, entry[2]): continue @@ -117,7 +118,7 @@ def print_tile(stmt, ic, x, y, tile, db):          bitinfo.append("")          extra_text = ""          for i in range(len(line)): -            if 36 <= i <= 45 and re.search(r"(logic_tile|dsp\d_tile|ipcon_tile)", stmt): +            if 36 <= i <= 45 and re_search_cached(r"(logic_tile|dsp\d_tile|ipcon_tile)", stmt):                  lutff_idx = k // 2                  lutff_bitnum = (i-36) + 10*(k%2)                  if line[i] == "1": diff --git a/icebox/icebox_hlc2asc.py b/icebox/icebox_hlc2asc.py index 8b82132..a95f610 100755 --- a/icebox/icebox_hlc2asc.py +++ b/icebox/icebox_hlc2asc.py @@ -15,6 +15,7 @@  import getopt, os, re, sys  import icebox +from icebox import re_match_cached, re_sub_cached  ## Get the tile-local name of a net. @@ -32,7 +33,7 @@ def untranslate_netname(x, y, fw, fh, net):              i = i + 1 - (i % 2) * 2          return g * group_size + i -    match = re.match(r'span4_y(\d+)_g(\d+)_(\d+)$', net) +    match = re_match_cached(r'span4_y(\d+)_g(\d+)_(\d+)$', net)      if match is not None:          my = int(match.group(1))          mw = int(match.group(2)) @@ -53,7 +54,7 @@ def untranslate_netname(x, y, fw, fh, net):          else:              return 'sp4_h_r_%d' % index(mg, mi, 12) -    match = re.match(r'span4_x(\d+)_g(\d+)_(\d+)$', net) +    match = re_match_cached(r'span4_x(\d+)_g(\d+)_(\d+)$', net)      if match is not None:          mx = int(match.group(1))          mw = int(match.group(2)) @@ -77,7 +78,7 @@ def untranslate_netname(x, y, fw, fh, net):          else:              return 'sp4_v_b_%d' % index(mg, mi, 12) -    match = re.match(r'dummy_y(\d+)_g(\d+)_(\d+)$', net) +    match = re_match_cached(r'dummy_y(\d+)_g(\d+)_(\d+)$', net)      if match is not None:          my = int(match.group(1))          mw = int(match.group(2)) @@ -89,7 +90,7 @@ def untranslate_netname(x, y, fw, fh, net):          return 'sp4_r_v_b_%d' % index(mg, mi, 12) -    match = re.match(r'span12_y(\d+)_g(\d+)_(\d+)$', net) +    match = re_match_cached(r'span12_y(\d+)_g(\d+)_(\d+)$', net)      if match is not None:          my = int(match.group(1))          mw = int(match.group(2)) @@ -110,7 +111,7 @@ def untranslate_netname(x, y, fw, fh, net):          else:              return 'sp12_h_r_%d' % index(mg, mi, 2) -    match = re.match(r'span12_x(\d+)_g(\d+)_(\d+)$', net) +    match = re_match_cached(r'span12_x(\d+)_g(\d+)_(\d+)$', net)      if match is not None:          mx = int(match.group(1))          mw = int(match.group(2)) @@ -131,7 +132,7 @@ def untranslate_netname(x, y, fw, fh, net):          else:              return 'sp12_v_b_%d' % index(mg, mi, 2) -    match = re.match(r'span4_bottom_g(\d+)_(\d+)$', net) +    match = re_match_cached(r'span4_bottom_g(\d+)_(\d+)$', net)      if match is not None:          mw = int(match.group(1))          mi = int(match.group(2)) @@ -153,7 +154,7 @@ def untranslate_netname(x, y, fw, fh, net):                  assert fw - x + mg - 4 >= 0                  return 'span4_horz_r_%d' % (mg * 4 + mi) -    match = re.match(r'span4_left_g(\d+)_(\d+)$', net) +    match = re_match_cached(r'span4_left_g(\d+)_(\d+)$', net)      if match is not None:          mw = int(match.group(1))          mi = int(match.group(2)) @@ -181,7 +182,7 @@ def untranslate_netname(x, y, fw, fh, net):                  assert y + mg - 3 >= 0                  return 'span4_vert_b_%d' % (mg * 4 + mi) -    match = re.match(r'span4_right_g(\d+)_(\d+)$', net) +    match = re_match_cached(r'span4_right_g(\d+)_(\d+)$', net)      if match is not None:          mw = int(match.group(1))          mi = int(match.group(2)) @@ -204,7 +205,7 @@ def untranslate_netname(x, y, fw, fh, net):              assert y + mg < fh + 3              return 'span4_vert_b_%d' % (mg * 4 + mi) -    match = re.match(r'span4_top_g(\d+)_(\d+)$', net) +    match = re_match_cached(r'span4_top_g(\d+)_(\d+)$', net)      if match is not None:          mw = int(match.group(1))          mi = int(match.group(2)) @@ -231,7 +232,7 @@ def untranslate_netname(x, y, fw, fh, net):              assert x - mg + 1 < fw              return 'span4_horz_r_%d' % (mg * 4 + mi) -    match = re.match(r'span4_bottomright(\d+)_(\d+)$', net) +    match = re_match_cached(r'span4_bottomright(\d+)_(\d+)$', net)      if match is not None:          mw = int(match.group(1))          mi = int(match.group(2)) @@ -249,7 +250,7 @@ def untranslate_netname(x, y, fw, fh, net):              assert y + mg - 5 < 0              return 'span4_vert_b_%d' % (mg * 4 + mi) -    match = re.match(r'span4_topleft(\d+)_(\d+)$', net) +    match = re_match_cached(r'span4_topleft(\d+)_(\d+)$', net)      if match is not None:          mw = int(match.group(1))          mi = int(match.group(2)) @@ -506,9 +507,9 @@ def logic_expression_to_lut(s, args):  def parse_verilog_bitvector_to_bits(in_str):      #replace x with 0 -    in_str = re.sub('[xX]', '0', in_str) +    in_str = re_sub_cached('[xX]', '0', in_str) -    m = re.match("([0-9]+)'([hdob])([0-9a-fA-F]+)", in_str) +    m = re_match_cached("([0-9]+)'([hdob])([0-9a-fA-F]+)", in_str)      if m:          num_bits = int(m.group(1))          prefix = m.group(2) @@ -773,7 +774,7 @@ class Tile:          bits_clear = set()          for bit in bits: -            match = re.match(r'(!?)B(\d+)\[(\d+)\]$', bit) +            match = re_match_cached(r'(!?)B(\d+)\[(\d+)\]$', bit)              if not match:                  raise ValueError("invalid bit description: %s" % bit)              if match.group(1): @@ -878,7 +879,7 @@ class LogicCell:          if fields[0] == 'lut' and len(fields) == 2:              self.lut_bits = fields[1]          elif fields[0] == 'out' and len(fields) >= 3 and fields[1] == '=': -            m = re.match("([0-9]+)'b([01]+)", fields[2]) +            m = re_match_cached("([0-9]+)'b([01]+)", fields[2])              if m:                  lut_bits = parse_verilog_bitvector_to_bits(fields[2])                  # Verilog 16'bXXXX is MSB first but the bitstream wants LSB. diff --git a/icebox/icebox_html.py b/icebox/icebox_html.py index 6415230..b710f61 100755 --- a/icebox/icebox_html.py +++ b/icebox/icebox_html.py @@ -16,6 +16,7 @@  #  import icebox +from icebox import re_match_cached, re_sub_cached  import getopt, sys, os, re  chipname = "iCE40 HX1K" @@ -252,7 +253,7 @@ configuration bits it has and how it is connected to its neighbourhood.</p>""" %          if not ic.tile_has_entry(tx, ty, entry):              continue          for bit in [bit.replace("!", "") for bit in entry[0]]: -            match = re.match(r"B(\d+)\[(\d+)\]$", bit) +            match = re_match_cached(r"B(\d+)\[(\d+)\]$", bit)              idx1 = int(match.group(1))              idx2 = int(match.group(2))              if entry[1] == "routing": @@ -324,7 +325,7 @@ nets are connected with nets from cells in its neighbourhood.</p>""")          for s in segs:              if s[0] == tx and s[1] == ty:                  this_segs.append(s[2]) -                match = re.match(r"(.*?_)(\d+)(.*)", s[2]) +                match = re_match_cached(r"(.*?_)(\d+)(.*)", s[2])                  if match:                      this_tile_nets.setdefault(match.group(1) + "*" + match.group(3), set()).add(int(match.group(2)))                  else: @@ -444,7 +445,7 @@ in the all-zeros configuration.</p>""")          for cfggrp in sorted(grpgrp[cat]):              grp = config_groups[cfggrp]              for bit in cfggrp.split(",")[1:]: -                match = re.match(r"B(\d+)\[(\d+)\]", bit) +                match = re_match_cached(r"B(\d+)\[(\d+)\]", bit)                  bits_in_cat.add((int(match.group(1)), int(match.group(2))))          print('<table style="font-size:x-small">') @@ -520,7 +521,7 @@ in the all-zeros configuration.</p>""")              bits = cfggrp.split(",")[1:]              print('<p><table style="font-size:small" border><tr>')              for bit in bits: -                print('<th style="width:5em"><a name="%s">%s</a></th>' % (re.sub(r"B(\d+)\[(\d+)\]", r"B.\1.\2", bit), bit)) +                print('<th style="width:5em"><a name="%s">%s</a></th>' % (re_sub_cached(r"B(\d+)\[(\d+)\]", r"B.\1.\2", bit), bit))              group_lines = list()              is_buffer = True              for entry in grp: @@ -569,7 +570,7 @@ in the all-zeros configuration.</p>""")      print('<p><table style="font-size:small" border><tr><th>Function</th><th>Bits</th></tr>')      for cfggrp in sorted(other_config_groups): -        bits = " ".join(['<a name="%s">%s</a>' % (re.sub(r"B(\d+)\[(\d+)\]", r"B.\1.\2", bit), bit) for bit in sorted(other_config_groups[cfggrp])]) +        bits = " ".join(['<a name="%s">%s</a>' % (re_sub_cached(r"B(\d+)\[(\d+)\]", r"B.\1.\2", bit), bit) for bit in sorted(other_config_groups[cfggrp])])          cfggrp = cfggrp.replace(" " + list(other_config_groups[cfggrp])[0], "")          print('<tr><td>%s</td><td>%s</td></tr>' % (cfggrp, bits))      print('</table></p>') diff --git a/icebox/icebox_maps.py b/icebox/icebox_maps.py index c791bea..35ff316 100755 --- a/icebox/icebox_maps.py +++ b/icebox/icebox_maps.py @@ -16,6 +16,7 @@  #  import icebox +from icebox import re_match_cached  import getopt, sys, re  mode = None @@ -58,7 +59,7 @@ def get_bit_group(x, y, db):                  funcs.add("r")              elif entry[1] == "buffer":                  funcs.add("b") -            elif re.match("LC_", entry[1]): +            elif re_match_cached("LC_", entry[1]):                  funcs.add("l")              elif entry[1] == "NegClk":                  funcs.add("N") @@ -94,7 +95,7 @@ def print_db_nets(stmt, db, pos):              if icebox.pos_has_net(pos[0], entry[3]): netnames.add(entry[3])      last_prefix = ""      for net in sorted(netnames, key=icebox.key_netname): -        match = re.match(r"(.*?)(\d+)$", net) +        match = re_match_cached(r"(.*?)(\d+)$", net)          if match:              if last_prefix == match.group(1):                  print(",%s" % match.group(2), end="") diff --git a/icebox/icebox_stat.py b/icebox/icebox_stat.py index ffb7b6a..ec404fb 100755 --- a/icebox/icebox_stat.py +++ b/icebox/icebox_stat.py @@ -16,6 +16,7 @@  #  import icebox +from icebox import re_match_cached  import getopt, sys, re  verbose = False @@ -72,23 +73,23 @@ if verbose:  for segs in connections:      for seg in segs:          if ic.tile_type(seg[0], seg[1]) == "IO" and seg[2].startswith("io_"): -            match = re.match("io_(\d+)/D_(IN|OUT)_(\d+)", seg[2]) +            match = re_match_cached("io_(\d+)/D_(IN|OUT)_(\d+)", seg[2])              if match:                  loc = (seg[0], seg[1], int(match.group(1)))                  io_locations.add(loc)          if ic.tile_type(seg[0], seg[1]) == "LOGIC" and seg[2].startswith("lutff_"): -            match = re.match("lutff_(\d)/in_\d", seg[2]) +            match = re_match_cached("lutff_(\d)/in_\d", seg[2])              if match:                  loc = (seg[0], seg[1], int(match.group(1)))                  lut_locations.add(loc) -            match = re.match("lutff_(\d)/cout", seg[2]) +            match = re_match_cached("lutff_(\d)/cout", seg[2])              if match:                  loc = (seg[0], seg[1], int(match.group(1)))                  carry_locations.add(loc) -            match = re.match("lutff_(\d)/out", seg[2]) +            match = re_match_cached("lutff_(\d)/out", seg[2])              if match:                  loc = (seg[0], seg[1], int(match.group(1)))                  seq_bits = icebox.get_lutff_seq_bits(ic.tile(loc[0], loc[1]), loc[2]) @@ -100,7 +101,7 @@ for segs in connections:              bram_locations.add(loc)          if seg[2].startswith("glb_netwk_"): -            match = re.match("glb_netwk_(\d)", seg[2]) +            match = re_match_cached("glb_netwk_(\d)", seg[2])              if match:                  global_nets.add(int(match.group(1))) @@ -108,7 +109,7 @@ pll_config_bitidx = dict()  for entry in icebox.iotile_l_db:      if entry[1] == "PLL": -        match = re.match(r"B(\d+)\[(\d+)\]", entry[0][0]); +        match = re_match_cached(r"B(\d+)\[(\d+)\]", entry[0][0]);          assert match          pll_config_bitidx[entry[2]] = (int(match.group(1)), int(match.group(2))) diff --git a/icebox/icebox_vlog.py b/icebox/icebox_vlog.py index 64e9ea5..184cb03 100755 --- a/icebox/icebox_vlog.py +++ b/icebox/icebox_vlog.py @@ -16,6 +16,7 @@  #  import icebox +from icebox import re_match_cached, re_sub_cached, re_search_cached  import getopt, sys, re  strip_comments = False @@ -93,22 +94,22 @@ for o, a in opts:      elif o in ("-p", "-P"):          with open(a, "r") as f:              for line in f: -                if o == "-P" and not re.search(" # ICE_(GB_)?IO", line): +                if o == "-P" and not re_search_cached(" # ICE_(GB_)?IO", line):                      continue -                line = re.sub(r"#.*", "", line.strip()).split() +                line = re_sub_cached(r"#.*", "", line.strip()).split()                  if "--warn-no-port" in line:                      line.remove("--warn-no-port")                  if len(line) and line[0] == "set_io":                      p = line[1]                      if o == "-P":                          p = p.lower() -                        p = re.sub(r"_ibuf$", "", p) -                        p = re.sub(r"_obuft$", "", p) -                        p = re.sub(r"_obuf$", "", p) -                        p = re.sub(r"_gb_io$", "", p) -                        p = re.sub(r"_pad(_[0-9]+|)$", r"\1", p) +                        p = re_sub_cached(r"_ibuf$", "", p) +                        p = re_sub_cached(r"_obuft$", "", p) +                        p = re_sub_cached(r"_obuf$", "", p) +                        p = re_sub_cached(r"_gb_io$", "", p) +                        p = re_sub_cached(r"_pad(_[0-9]+|)$", r"\1", p)                      portnames.add(p) -                    if not re.match(r"[a-zA-Z_][a-zA-Z0-9_]*$", p): +                    if not re_match_cached(r"[a-zA-Z_][a-zA-Z0-9_]*$", p):                          p = "\\%s " % p                      unmatched_ports.add(p)                      if len(line) > 3: @@ -177,7 +178,7 @@ pll_gbuf = dict()  for entry in icebox.iotile_l_db:      if entry[1] == "PLL": -        match = re.match(r"B(\d+)\[(\d+)\]", entry[0][0]); +        match = re_match_cached(r"B(\d+)\[(\d+)\]", entry[0][0]);          assert match          pll_config_bitidx[entry[2]] = (int(match.group(1)), int(match.group(2))) @@ -234,8 +235,8 @@ for idx, tile in list(ic.io_tiles.items()):              iocells_negclk.add((idx[0], idx[1], 0))              iocells_negclk.add((idx[0], idx[1], 1))          if entry[1].startswith("IOB_") and entry[2].startswith("PINTYPE_") and tc.match(entry[0]): -            match1 = re.match("IOB_(\d+)", entry[1]) -            match2 = re.match("PINTYPE_(\d+)", entry[2]) +            match1 = re_match_cached("IOB_(\d+)", entry[1]) +            match2 = re_match_cached("PINTYPE_(\d+)", entry[2])              assert match1 and match2              iocells_type[(idx[0], idx[1], int(match1.group(1)))][int(match2.group(1))] = "1"      iocells_type[(idx[0], idx[1], 0)] = "".join(iocells_type[(idx[0], idx[1], 0)]) @@ -244,7 +245,7 @@ for idx, tile in list(ic.io_tiles.items()):  for segs in sorted(ic.group_segments()):      for seg in segs:          if ic.tile_type(seg[0], seg[1]) == "IO": -            match = re.match("io_(\d+)/D_(IN|OUT)_(\d+)", seg[2]) +            match = re_match_cached("io_(\d+)/D_(IN|OUT)_(\d+)", seg[2])              if match:                  cell = (seg[0], seg[1], int(match.group(1)))                  if cell in iocells_skip: @@ -287,7 +288,7 @@ for segs in sorted(ic.group_segments(extra_connections=extra_connections, extra_      renamed_net_to_port = False      for s in segs: -        match =  re.match("io_(\d+)/PAD", s[2]) +        match =  re_match_cached("io_(\d+)/PAD", s[2])          if match:              idx = (s[0], s[1], int(match.group(1)))              p = "io_%d_%d_%d" % idx @@ -322,7 +323,7 @@ for segs in sorted(ic.group_segments(extra_connections=extra_connections, extra_                  text_ports.append("inout %s" % p)                  text_wires.append("assign %s = %s;" % (p, n)) -        match =  re.match("lutff_(\d+)/", s[2]) +        match =  re_match_cached("lutff_(\d+)/", s[2])          if match:              #IpCon and DSP tiles look like logic tiles, but aren't.              if ic.device in ["5k", "u4k"] and (s[0] == 0 or s[0] == ic.max_x): @@ -347,10 +348,10 @@ for segs in sorted(ic.group_segments(extra_connections=extra_connections, extra_      count_drivers = []      for s in segs: -        if re.match(r"ram/RDATA_", s[2]): count_drivers.append(s[2]) -        if re.match(r"io_./D_IN_", s[2]): count_drivers.append(s[2]) -        if re.match(r"lutff_./out", s[2]): count_drivers.append(s[2]) -        if re.match(r"lutff_./lout", s[2]): count_drivers.append(s[2]) +        if re_match_cached(r"ram/RDATA_", s[2]): count_drivers.append(s[2]) +        if re_match_cached(r"io_./D_IN_", s[2]): count_drivers.append(s[2]) +        if re_match_cached(r"lutff_./out", s[2]): count_drivers.append(s[2]) +        if re_match_cached(r"lutff_./lout", s[2]): count_drivers.append(s[2])      if len(count_drivers) != 1 and check_driver:          failed_drivers_check.append((n, count_drivers)) @@ -870,7 +871,7 @@ if do_collect:      vec_ports_max = dict()      vec_ports_dir = dict()      for port in text_ports: -        match = re.match(r"(input|output|inout) (.*)\[(\d+)\] ?$", port); +        match = re_match_cached(r"(input|output|inout) (.*)\[(\d+)\] ?$", port);          if match:              vec_ports_min[match.group(2)] = min(vec_ports_min.setdefault(match.group(2), int(match.group(3))), int(match.group(3)))              vec_ports_max[match.group(2)] = max(vec_ports_max.setdefault(match.group(2), int(match.group(3))), int(match.group(3))) @@ -889,7 +890,7 @@ new_text_wires = list()  new_text_regs = list()  new_text_raw = list()  for line in text_wires: -    match = re.match(r"wire ([^ ;]+)(.*)", line) +    match = re_match_cached(r"wire ([^ ;]+)(.*)", line)      if match:          if strip_comments:              name = match.group(1) | 
