Iteration on tileconn, pruning approach seems to be working.

Signed-off-by: Keith Rothman <537074+litghost@users.noreply.github.com>
diff --git a/fuzzers/004-tileinfo/analyze_errors.py b/fuzzers/004-tileinfo/analyze_errors.py
index 061c9e5..cbe55cc 100644
--- a/fuzzers/004-tileinfo/analyze_errors.py
+++ b/fuzzers/004-tileinfo/analyze_errors.py
@@ -1,5 +1,6 @@
-import json
 import argparse
+import json
+import pprint
 
 
 def main():
@@ -33,7 +34,7 @@
             combined_generated_nodes |= set(generated_node)
 
         # Make sure there are not extra wires in nodes.
-        assert error['raw_node'] == combined_generated_nodes, (node, error)
+        assert error['raw_node'] == combined_generated_nodes, pprint.pformat((node, error))
 
         good_node = max(error['generated_nodes'], key=lambda x: len(x))
         bad_nodes = error['generated_nodes'] - set((good_node, ))
diff --git a/fuzzers/004-tileinfo/generate_grid.py b/fuzzers/004-tileinfo/generate_grid.py
index 911c30f..5235c6a 100644
--- a/fuzzers/004-tileinfo/generate_grid.py
+++ b/fuzzers/004-tileinfo/generate_grid.py
@@ -10,11 +10,13 @@
 import datetime
 import pickle
 import sys
-import copy
+from collections import namedtuple
 
-from utils import util, lib
+from utils import util, lib, tile_sizes, connections
 from utils.xjson import extract_numbers
 
+TileConnection = namedtuple('TileConnection', 'grid_deltas tile_types wire_pair')
+
 
 def get_tile_grid_info(fname):
     with open(fname, 'r') as f:
@@ -232,12 +234,6 @@
                  node_tree, tiles, x_wires, y_wires, wires_in_node):
     """ Check if two wires are directly connected. """
 
-    next_wire_in_dim = next_wire_in_dimension(wire1, tile1, wire2, tile2,
-                                              tiles, x_wires, y_wires,
-                                              wire_map, wires_in_node)
-    if next_wire_in_dim is not None:
-        return next_wire_in_dim
-
     # Because there are multiple possible wire connections between these two
     # tiles, consult the node_tree to determine if the two wires are actually connected.
     #
@@ -263,93 +259,10 @@
 
     # The node_tree didn't specify these wires, and the wires are not
     # unambiguously connected.
-    return False
+    return None
 
 
-def edge_overlap(low1, high1, low2, high2):
-    """ Returns true if two lines have >0 overlap
-
-    >>> edge_overlap(0, 1, 1, 2)
-    False
-    >>> edge_overlap(0, 2, 1, 2)
-    True
-    >>> edge_overlap(1, 2, 1, 2)
-    True
-    >>> edge_overlap(1, 2, 0, 1)
-    False
-    >>> edge_overlap(1, 2, 0, 2)
-    True
-    >>> edge_overlap(0, 1, 0, 1)
-    True
-    """
-    if low1 < low2:
-        return low2 < high1
-    else:
-        return low1 < high2
-
-def box_share_edge(box1, box2):
-    """ Return true if the two boxes share any edge.
-
-    >>> box_share_edge(((0, 1), (0, 1)), ((0, 1), (1, 2)))
-    True
-    >>> box_share_edge(((0, 1), (0, 1)), ((1, 2), (1, 2)))
-    False
-    >>> box_share_edge(((0, 1), (0, 3)), ((1, 2), (2, 5)))
-    True
-    >>> box_share_edge(((0, 1), (0, 3)), ((1, 2), (3, 6)))
-    False
-    >>> box_share_edge(((0, 3), (0, 1)), ((2, 5), (1, 2)))
-    True
-    >>> box_share_edge(((0, 3), (0, 1)), ((3, 6), (1, 2)))
-    False
-    >>> box_share_edge(((0, 3), (0, 3)), ((0, 3), (3, 6)))
-    True
-    >>> box_share_edge(((0, 3), (0, 3)), ((3, 6), (0, 3)))
-    True
-    >>> box_share_edge(((0, 3), (0, 3)), ((3, 6), (3, 6)))
-    False
-
-    """
-    ((box1_xlow, box1_xhigh), (box1_ylow, box1_yhigh)) = box1
-    ((box2_xlow, box2_xhigh), (box2_ylow, box2_yhigh)) = box2
-
-    if box1_xlow == box2_xhigh or box2_xlow == box1_xhigh:
-        # box 1 left edge may touch box 2 right edge
-        #  or
-        # box 2 left edge may touch box 1 right edge
-        if edge_overlap(box1_ylow, box1_yhigh, box2_ylow, box2_yhigh):
-            return True
-
-    if box1_ylow == box2_yhigh or box2_ylow == box1_yhigh:
-        # box 1 bottom edge may touch box 1 top edge
-        #  or
-        # box 2 bottom edge may touch box 2 top edge
-        if edge_overlap(box1_xlow, box1_xhigh, box2_xlow, box2_xhigh):
-            return True
-
-    return False
-
-
-def tiles_are_adjcent(tile1, tile2, tile_type_sizes):
-    width1, height1 = tile_type_sizes[tile1['type']]
-    tile1_xlow = tile1['grid_x']
-    tile1_ylow = tile1['grid_y'] - height1
-    tile1_xhigh = tile1['grid_x'] + width1
-    tile1_yhigh = tile1['grid_y']
-
-    width2, height2 = tile_type_sizes[tile2['type']]
-    tile2_xlow = tile2['grid_x']
-    tile2_ylow = tile2['grid_y'] - height2
-    tile2_xhigh = tile2['grid_x'] + width2
-    tile2_yhigh = tile2['grid_y']
-
-    return box_share_edge(
-            ((tile1_xlow, tile1_xhigh), (tile1_ylow, tile1_yhigh)),
-            ((tile2_xlow, tile2_xhigh), (tile2_ylow, tile2_yhigh)),
-            )
-
-
-def process_node(tileconn, key_history, node, wire_map, node_tree, tiles, tile_type_sizes):
+def process_node(tileconn, key_history, node, wire_map, node_tree, tiles, tile_type_sizes, revisit_list):
     wires = [wire['wire'] for wire in node['wires']]
 
     wires_in_tiles = {}
@@ -389,14 +302,21 @@
             tile1 = tiles[wire_info1['tile']]
             tile2 = tiles[wire_info2['tile']]
 
-            if not tiles_are_adjcent(tile1, tile2, tile_type_sizes):
-                continue
-
-            if not is_connected(wire1, wire_info1['tile'], wire2,
+            result = is_connected(wire1, wire_info1['tile'], wire2,
                                 wire_info2['tile'], node, wires_in_tiles,
                                 wire_map, node_tree, tiles, x_wires, y_wires,
-                                wires):
-                continue
+                                wires)
+            if result is not None:
+                if not result:
+                    continue
+            else:
+                # This wire is an unknown, come back to it
+                dx = tile2['grid_x'] - tile1['grid_x']
+                dy = tile2['grid_y'] - tile1['grid_y']
+                same_tile = dx == 0 and dy == 0
+                if same_tile or tile_sizes.tiles_are_adjcent(tile1, tile2, tile_type_sizes):
+                    revisit_list.append((wire1, wire2))
+                    continue
 
             update_tile_conn(tileconn, key_history, wire1, wire_info1, wire2,
                              wire_info2, tiles)
@@ -413,20 +333,20 @@
         (wire2['type'], wire2['shortname'], tile2['grid_x'], tile2['grid_y'])):
         wire1, tile1, wire2, tile2 = wire2, tile2, wire1, tile1
 
-    tileconn.append({
-        "grid_deltas": [
+    tileconn.append(TileConnection(
+        grid_deltas=(
             tile2['grid_x'] - tile1['grid_x'],
             tile2['grid_y'] - tile1['grid_y'],
-        ],
-        "tile_types": [
+        ),
+        tile_types=(
             tile1['type'],
             tile2['type'],
-        ],
-        "wire_pair": [
+        ),
+        wire_pair=(
             wire1['shortname'],
             wire2['shortname'],
-        ],
-    })
+        ),
+    ))
 
 
 def flatten_tile_conn(tileconn):
@@ -437,16 +357,16 @@
     flat_tileconn = {}
 
     for conn in tileconn:
-        key = (tuple(conn['tile_types']), tuple(conn['grid_deltas']))
+        key = (tuple(conn.tile_types), tuple(conn.grid_deltas))
 
         if key not in flat_tileconn:
             flat_tileconn[key] = {
-                'tile_types': conn['tile_types'],
-                'grid_deltas': conn['grid_deltas'],
+                'tile_types': conn.tile_types,
+                'grid_deltas': conn.grid_deltas,
                 'wire_pairs': set()
             }
 
-        flat_tileconn[key]['wire_pairs'].add(tuple(conn['wire_pair']))
+        flat_tileconn[key]['wire_pairs'].add(tuple(conn.wire_pair))
 
     def inner():
         for output in flat_tileconn.values():
@@ -528,43 +448,26 @@
 def connect_wires(tiles, tileconn, wire_map):
     """ Connect individual wires into groups of wires called nodes. """
 
+    # Map of tile type to list of wires in tile type
+    tile_wires = {}
+    for tile_info in tiles.values():
+        tile_wires[tile_info['type']] = set()
+
+    for wire, wire_info in wire_map.items():
+        tile = tiles[wire_info['tile']]
+        tile_type = tile['type']
+        tile_wires[tile_type].add(wire_info['shortname'])
+
     # Initialize all nodes to originally only contain the wire by itself.
     wire_nodes = {}
     for wire in wire_map:
         wire_nodes[wire] = set([wire])
 
-    wire_connection_map = {}
-    for conn in tileconn:
-        for idx, (wire1, wire2) in enumerate(conn['wire_pairs']):
-            key1 = (conn['tile_types'][0], wire1)
-            if key1 not in wire_connection_map:
-                wire_connection_map[key1] = []
-            wire_connection_map[key1].append((conn, idx))
-
-            key2 = (conn['tile_types'][1], wire2)
-            if key2 not in wire_connection_map:
-                wire_connection_map[key2] = []
-            wire_connection_map[key2].append((conn, idx))
-
-    coord_to_tile = create_coord_to_tile(tiles)
-
-    for wire, wire_info in progressbar.progressbar(wire_map.items()):
-        key = (wire_info['type'], wire_info['shortname'])
-        if key not in wire_connection_map:
-            continue
-
-        for conn, idx in wire_connection_map[key]:
-            for target_tile, target_wire in get_connections(
-                    wire, wire_info, conn, idx, coord_to_tile, tiles):
-
-                full_wire_name = coord_to_tile[target_tile] + '/' + target_wire
-                assert wire_map[full_wire_name]['shortname'] == target_wire, (
-                    target_tile, target_wire, wire, conn)
-                assert wire_map[full_wire_name]['tile'] == coord_to_tile[
-                    target_tile], (wire_map[full_wire_name]['tile'],
-                                   coord_to_tile[target_tile])
-
-                make_connection(wire_nodes, wire, full_wire_name)
+    conns = connections.Connections(tiles, tileconn, tile_wires)
+    for wire_a, wire_b in conns.get_connections():
+        full_wire_a = wire_a.tile + '/' + wire_a.wire
+        full_wire_b = wire_b.tile + '/' + wire_b.wire
+        make_connection(wire_nodes, full_wire_a, full_wire_b)
 
     # Find unique nodes
     nodes = {}
@@ -616,10 +519,144 @@
     return grid, wire_map
 
 
+def revisit_wires(tileconn, nodes, revisit_list, key_history, wire_map, grid):
+    interesting_wires = {}
+
+    for wire1, wire2 in revisit_list:
+        wire_info1 = wire_map[wire1]
+        wire_info2 = wire_map[wire2]
+
+        tile1 = grid[wire_info1['tile']]
+        tile2 = grid[wire_info2['tile']]
+
+        interesting_wires[tile1['type'], wire_info1['shortname']] = set()
+        interesting_wires[tile2['type'], wire_info2['shortname']] = set()
+
+    for conn in tileconn:
+        for idx, k in enumerate(zip(conn.tile_types, conn.wire_pair)):
+            k = tuple(k)
+            if k in interesting_wires:
+                if sum(map(abs, conn.grid_deltas)) != 1:
+                    continue
+
+                if idx == 0:
+                    interesting_wires[k].add(conn.grid_deltas)
+                else:
+                    interesting_wires[k].add((-conn.grid_deltas[0], -conn.grid_deltas[1]))
+
+    print('{} 1. Remaining {} revisit pairs'.format(datetime.datetime.now(), len(revisit_list)))
+    revisit_list2 = []
+    for wire1, wire2 in revisit_list:
+        wire_info1 = wire_map[wire1]
+        wire_info2 = wire_map[wire2]
+
+        tile1 = grid[wire_info1['tile']]
+        tile2 = grid[wire_info2['tile']]
+
+        key1 = tile1['type'], wire_info1['shortname']
+        key2 = tile2['type'], wire_info2['shortname']
+
+        wire1_directions = interesting_wires[key1]
+        wire2_directions = interesting_wires[key2]
+
+        dx = tile2['grid_x'] - tile1['grid_x']
+        dy = tile2['grid_y'] - tile1['grid_y']
+
+        paired_wires = False
+        if (dx, dy) in wire1_directions and len(wire2_directions) == 0:
+            interesting_wires[key2].add((-dx, -dy))
+            paired_wires = True
+
+        if (-dx, -dy) in wire2_directions and len(wire1_directions) == 0:
+            interesting_wires[key1].add((dx, dy))
+            paired_wires = True
+
+        if (dx, dy) in wire1_directions and (-dx, -dy) in wire2_directions:
+            paired_wires = True
+
+        if paired_wires:
+            update_tile_conn(
+                    tileconn, key_history,
+                    wire1, wire_info1,
+                    wire2, wire_info2,
+                    grid)
+        else:
+            revisit_list2.append((wire1, wire2))
+
+
+    print('{} 2. Remaining {} revisit pairs'.format(datetime.datetime.now(), len(revisit_list2)))
+
+    tileconn = list(set(tileconn))
+
+    print('{} Pruning bad connections, have {} connections'.format(datetime.datetime.now(), len(tileconn)))
+
+    # Construct map from (tile_type, wire) -> list of tiles
+    tile_type_instances = {}
+    tile_at_loc = {}
+    for tile, gridinfo in grid.items():
+        if gridinfo['type'] not in tile_type_instances:
+            tile_type_instances[gridinfo['type']] = []
+        tile_type_instances[gridinfo['type']].append(tile)
+
+        loc = gridinfo['grid_x'], gridinfo['grid_y']
+        assert loc not in tile_at_loc
+        tile_at_loc[loc] = tile
+
+    # Prune bad connections
+    bad_connections = set()
+    for idx, conn in enumerate(progressbar.progressbar(tileconn)):
+        tile1_type, tile2_type = conn.tile_types
+        wire1, wire2 = conn.wire_pair
+
+        for tile1 in tile_type_instances[tile1_type]:
+            tile1_info = grid[tile1]
+            assert tile1_info['type'] == tile1_type
+
+            dx, dy = conn.grid_deltas
+            tile2_grid_x = tile1_info['grid_x'] + dx
+            tile2_grid_y = tile1_info['grid_y'] + dy
+
+            loc = tile2_grid_x, tile2_grid_y
+            if loc not in tile_at_loc:
+                continue
+
+            tile2 = tile_at_loc[loc]
+            tile2_info = grid[tile2]
+
+            if tile2_info['type'] != tile2_type:
+                continue
+
+            fullwire1 = tile1 + '/' + wire1
+            fullwire2 = tile2 + '/' + wire2
+
+            if fullwire1 not in nodes or fullwire2 not in nodes:
+                bad_connections.add(idx)
+                continue
+
+            # tileconn says these two wires should form a node.  If these wires
+            # are a node, they will have the same set object representing
+            # the node.
+            if nodes[fullwire1] is not nodes[fullwire2]:
+                bad_connections.add(idx)
+
+    print('{} Found {} bad connections'.format(datetime.datetime.now(), len(bad_connections)))
+
+    # Delete from highest index to lowest index to preverse deletion order
+    for idx in sorted(bad_connections, reverse=True):
+        del tileconn[idx]
+
+    print('{} Removed {} bad connections'.format(datetime.datetime.now(), len(bad_connections)))
+
+    return tileconn
+
+
 def generate_tileconn(pool, node_tree, nodes, wire_map, grid, tile_type_sizes):
     tileconn = []
     key_history = {}
     raw_node_data = []
+    revisit_list = []
+
+    wire_nodes = {}
     with progressbar.ProgressBar(max_value=len(nodes)) as bar:
         for idx, node in enumerate(
                 pool.imap_unordered(
@@ -630,167 +667,21 @@
             bar.update(idx)
             raw_node_data.append(node)
             process_node(tileconn, key_history, node, wire_map, node_tree,
-                         grid, tile_type_sizes)
+                         grid, tile_type_sizes, revisit_list)
             bar.update(idx + 1)
 
+            node = set(wire['wire'] for wire in node['wires'])
+            for wire in node:
+                assert wire not in wire_nodes
+                wire_nodes[wire] = node
+
+    tileconn = revisit_wires(tileconn, wire_nodes, revisit_list, key_history, wire_map, grid)
+
     tileconn = flatten_tile_conn(tileconn)
 
     return tileconn, raw_node_data
 
 
-def max_size_for_tile(tile, grid, tile_by_loc, rclk_rows):
-    """ Guess maximum size for a tile. """
-    tile_type = grid[tile]['type']
-    if tile_type== 'NULL':
-        return (1, 1)
-
-    # Pos X, Neg Y
-    base_grid_x = grid[tile]['grid_x']
-    base_grid_y = grid[tile]['grid_y']
-
-    # Walk up X
-    grid_x = base_grid_x
-    grid_y = base_grid_y
-    while True:
-        try:
-            next_tile = tile_by_loc[(grid_x+1, grid_y)]
-        except KeyError:
-            break
-
-        if grid[next_tile]['type'] != 'NULL':
-            break
-
-        grid_x += 1
-
-    max_grid_x = grid_x
-
-    # Walk down Y
-    grid_x = base_grid_x
-    grid_y = base_grid_y
-    while True:
-        # Most tiles don't control the RCLK row, but a handful of tiles do!
-        if grid_y-1 in rclk_rows and tile_type not in [
-                "CFRM_AMS_CFGIO",
-                "PSS_ALTO",
-                "CFG_CONFIG",
-                ]:
-            break
-
-        try:
-            next_tile = tile_by_loc[(grid_x, grid_y-1)]
-        except KeyError:
-            break
-
-        if grid[next_tile]['type'] != 'NULL':
-            break
-
-        grid_y -= 1
-
-    max_grid_y = grid_y
-
-    return (max_grid_x-base_grid_x+1), (base_grid_y-max_grid_y+1)
-
-
-def generate_tile_type_sizes(grid):
-    """ Generate tile sizes for the given grid using max_size_for_tile. """
-    tile_type_sizes = {}
-
-    tile_by_loc = {}
-
-    rclk_rows = set()
-
-    for tile, gridinfo in grid.items():
-        key = gridinfo['grid_x'], gridinfo['grid_y']
-        assert key not in tile_by_loc
-        tile_by_loc[key] = tile
-
-        if gridinfo['type'] in ['RCLK_INT_R', 'RCLK_INT_L']:
-            rclk_rows.add(gridinfo['grid_y'])
-
-    for tile, gridinfo in grid.items():
-        size_x, size_y = max_size_for_tile(tile, grid, tile_by_loc, rclk_rows)
-        if gridinfo['type'] not in tile_type_sizes:
-            tile_type_sizes[gridinfo['type']] = (size_x, size_y)
-        else:
-            old_size_x, old_size_y = tile_type_sizes[gridinfo['type']]
-            tile_type_sizes[gridinfo['type']] = (
-                    min(size_x, old_size_x),
-                    min(size_y, old_size_y),
-                    )
-
-    return tile_type_sizes
-
-
-def commit_tile_type(grid, tile_type, size):
-    """ First verify that expanding the specified tile type by the size
-    specified works, then modify the tile grid to reflect the expanded sizes.
-    """
-    gridinfo_by_loc = {}
-    locs = []
-    for tile in grid:
-        gridinfo = grid[tile]
-        key = gridinfo['grid_x'], gridinfo['grid_y']
-        assert key not in gridinfo_by_loc
-        gridinfo_by_loc[key] = gridinfo
-
-        if gridinfo['type'] == tile_type:
-            locs.append(key)
-
-    for loc in locs:
-        for dx in range(size[0]):
-            for dy in range(size[1]):
-                key = loc[0] + dx, loc[1] - dy
-
-                if dx == 0 and dy == 0:
-                    assert gridinfo_by_loc[key]['type'] == tile_type
-                else:
-                    assert gridinfo_by_loc[key]['type'] == 'NULL', (
-                            loc, key, tile_type, gridinfo_by_loc[key]['type'])
-
-    for loc in locs:
-        for dx in range(size[0]):
-            for dy in range(size[1]):
-                    gridinfo_by_loc[key]['type'] = 'expanded_' + tile_type
-
-
-def guess_tile_type_sizes(grid):
-    """ Guess tile type sizes by expanding, and then blocking by largest."""
-    grid = copy.deepcopy(grid)
-    tile_type_sizes = generate_tile_type_sizes(grid)
-    commited_types = set()
-
-    # Commit longest or widest tiles first
-    tile_type_by_size = sorted(tile_type_sizes, key=lambda k: max(tile_type_sizes[k][0],tile_type_sizes[k][1]), reverse=True)
-    for tile_type in tile_type_by_size:
-        if tile_type_sizes[tile_type] == (1, 1):
-            continue
-
-        recompute_sizes = False
-        try:
-            # Attempt to commit this tile type
-            commit_tile_type(grid, tile_type, tile_type_sizes[tile_type])
-        except AssertionError:
-            # Commit failed, try to recompute and then re-commit.
-            recompute_sizes = True
-
-        if recompute_sizes:
-            new_tile_type_sizes = generate_tile_type_sizes(grid)
-
-            for tile_type in new_tile_type_sizes:
-                if tile_type in commited_types:
-                    continue
-                if tile_type.startswith('expanded_'):
-                    continue
-
-                tile_type_sizes[tile_type] = new_tile_type_sizes[tile_type]
-
-            commit_tile_type(grid, tile_type, tile_type_sizes[tile_type])
-
-        commited_types.add(tile_type)
-
-    return tile_type_sizes
-
-
 def main():
     parser = argparse.ArgumentParser(
         description=
@@ -846,7 +737,7 @@
         with open(node_tree_file) as f:
             node_tree = json.load(f)
 
-        tile_type_sizes = guess_tile_type_sizes(grid)
+        tile_type_sizes = tile_sizes.guess_tile_type_sizes(grid)
 
         print('{} Creating tile connections'.format(datetime.datetime.now()))
         tileconn, raw_node_data = generate_tileconn(pool, node_tree, nodes,
diff --git a/utils/lib.py b/utils/lib.py
index 94ada84..a8f421f 100644
--- a/utils/lib.py
+++ b/utils/lib.py
@@ -1,6 +1,7 @@
 import os.path
 import csv
 import pickle
+import pprint
 import re
 from collections import namedtuple
 
@@ -86,7 +87,7 @@
             combined_generated_nodes |= set(generated_node)
 
         # Make sure there are not extra wires in nodes.
-        assert error['raw_node'] == combined_generated_nodes, (node, error)
+        assert error['raw_node'] == combined_generated_nodes, pprint.pformat((node, error))
 
         good_node = max(error['generated_nodes'], key=lambda x: len(x))
         bad_nodes = error['generated_nodes'] - set((good_node, ))