From 52b8694707053b5138248da01fa75974c51f6284 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 5 Dec 2024 15:00:42 -0800 Subject: [PATCH 1/5] HW to give North I/O tiles SB --- canal/circuit.py | 41 ++++++++++++++++++++++++++++++++++++++++- canal/interconnect.py | 30 +++++++++++++++++++++++++----- canal/util.py | 35 ++++++++++++++++++++++++++--------- 3 files changed, 91 insertions(+), 15 deletions(-) diff --git a/canal/circuit.py b/canal/circuit.py index 0304712..a7259c2 100644 --- a/canal/circuit.py +++ b/canal/circuit.py @@ -625,7 +625,8 @@ def __init__(self, tiles: Dict[int, Tile], full_config_addr_width: int = 32, stall_signal_width: int = 4, double_buffer: bool = False, - ready_valid: bool = False): + ready_valid: bool = False, + give_north_io_sbs: bool = False): super().__init__() # turn off hashing because we controls that hashing here @@ -700,6 +701,44 @@ def __init__(self, tiles: Dict[int, Tile], for bit_width, tile in self.tiles.items(): # connection box time for port_name, port_node in tile.ports.items(): + + if give_north_io_sbs: + # Lift up if io2glb or glb2io port (and skip the rest i.e., adding SB and CB connections) + if "glb2io" in port_name: + ready_port_name = port_name + "_ready" + valid_port_name = port_name + "_valid" + + core_port = self.__get_port(port_name) + core_ready_port = self.__get_port(ready_port_name) + core_valid_port = self.__get_port(valid_port_name) + + self.add_port(port_name, magma.In(core_port.base_type())) + self.add_port(valid_port_name, magma.In(magma.Bit)) + self.add_port(ready_port_name, magma.Out(magma.Bit)) + + self.wire(self.ports[port_name], core_port) + self.wire(self.convert(self.ports[valid_port_name], magma.Bits[1]), core_valid_port) + self.wire(self.convert(core_ready_port, magma.bit), self.ports[ready_port_name]) + + continue + + if "io2glb" in port_name: + ready_port_name = port_name + "_ready" + valid_port_name = port_name + "_valid" + + core_port = self.__get_port(port_name) + core_ready_port = self.__get_port(ready_port_name) + core_valid_port = self.__get_port(valid_port_name) + + self.add_port(port_name, magma.Out(core_port.base_type())) + self.add_port(valid_port_name, magma.Out(magma.Bit)) + self.add_port(ready_port_name, magma.In(magma.Bit)) + + self.wire(core_port, self.ports[port_name]) + self.wire(self.convert(core_valid_port, magma.bit), self.ports[valid_port_name]) + self.wire(self.convert(self.ports[ready_port_name], magma.Bits[1]), core_ready_port) + continue + # input ports if len(port_node) == 0: assert bit_width == port_node.width diff --git a/canal/interconnect.py b/canal/interconnect.py index 0dfed1d..944ca58 100644 --- a/canal/interconnect.py +++ b/canal/interconnect.py @@ -25,7 +25,8 @@ def __init__(self, interconnects: Dict[int, InterconnectGraph], stall_signal_width: int = 4, lift_ports=False, double_buffer: bool = False, - ready_valid: bool = False): + ready_valid: bool = False, + give_north_io_sbs: bool = False): super().__init__() self.__interface = {} @@ -43,6 +44,8 @@ def __init__(self, interconnects: Dict[int, InterconnectGraph], self.double_buffer = double_buffer self.ready_valid = ready_valid + self.give_north_io_sbs = give_north_io_sbs + # loop through the grid and create tile circuits # first find all the coordinates coordinates = OrderedSet() @@ -84,7 +87,8 @@ def __init__(self, interconnects: Dict[int, InterconnectGraph], TileCircuit(tiles, config_addr_width, config_data_width, stall_signal_width=stall_signal_width, double_buffer=self.double_buffer, - ready_valid=self.ready_valid) + ready_valid=self.ready_valid, + give_north_io_sbs=self.give_north_io_sbs) # we need to deal with inter-tile connections now # we only limit mesh @@ -264,15 +268,30 @@ def __lift_ports(self): self.wire(p, tile.ports[sb_name + "_ready"]) self.__interface[ready_name] = sb_port + + def skip_margin_connection(self, tile): + if self.give_north_io_sbs: + return tile.switchbox.num_track > 0 and tile.y != 0 + else: + return tile.switchbox.num_track > 0 + + # FIXME: This is a hack. Why are io2f and f2io tiles ports in the first place? They shouldn't be... + def dont_lift_port(self, tile, port_name): + # dont lift f2io and io2f ports to interconnect level since these connect to the SB within the I/O tile + return self.give_north_io_sbs and (tile.y == 0 and ("f2io" in port_name or "io2f" in port_name)) + + # This function makes the tile-to-tile connection for the margin tiles + # And lifts up ports at the "edge" of the Interconnect graph as ports for the + # Interconnect module def __connect_margin_tiles(self): # connect these margin tiles # margin tiles have empty switchbox for coord, tile_dict in self.__tiles.items(): for bit_width, tile in tile_dict.items(): - if tile.switchbox.num_track > 0 or tile.core is None: + if self.skip_margin_connection(tile) or tile.core is None: continue for port_name, port_node in tile.ports.items(): - if port_name == "flush": + if port_name == "flush" or self.dont_lift_port(tile, port_name): continue tile_port = self.tile_circuits[coord].ports[port_name] # FIXME: this is a hack @@ -719,7 +738,8 @@ def clone(self): self.tile_id_width, self.stall_signal_width, self.__lifted_ports, - double_buffer=self.double_buffer) + double_buffer=self.double_buffer, + give_north_io_sbs=self.give_north_io_sbs) return ic def get_column(self, x: int): diff --git a/canal/util.py b/canal/util.py index ec3e380..78881a4 100644 --- a/canal/util.py +++ b/canal/util.py @@ -58,6 +58,7 @@ def create_uniform_interconnect(width: int, List[Tuple[int, SwitchBoxSide]] = None, io_sides: List[IOSide] = [IOSide.None_], io_conn: Dict[str, Dict[str, List[int]]] = None, + give_north_io_sbs: bool = False, additional_core_fn: Callable[[int, int], Core] = lambda _, __: None, inter_core_connection: Dict[str, List[str]] = None ) -> InterconnectGraph: @@ -96,9 +97,15 @@ def create_uniform_interconnect(width: int, if IOSide.None_ in io_sides: io_sides = [IOSide.None_] x_min, x_max, y_min, y_max = get_array_size(width, height, io_sides) + + interconnect_x_min = x_min + interconnect_x_max = x_max + interconnect_y_min = y_min-1 if give_north_io_sbs else y_min + interconnect_y_max = y_max + # create tiles and set cores - for x in range(x_min, x_max + 1): - for y in range(y_min, y_max + 1, tile_height): + for x in range(interconnect_x_min, interconnect_x_max + 1): + for y in range(interconnect_y_min, interconnect_y_max + 1, tile_height): # compute the number of tracks num_track = compute_num_tracks(x_min, y_min, x, y, track_info) @@ -158,15 +165,15 @@ def create_uniform_interconnect(width: int, current_track = 0 for track_len in track_lens: for _ in range(track_info[track_len]): - interconnect.connect_switchbox(x_min, y_min, x_max, - y_max, + interconnect.connect_switchbox(interconnect_x_min, interconnect_y_min, interconnect_x_max, + interconnect_y_max, track_len, current_track, InterconnectPolicy.Ignore) current_track += 1 # insert io - connect_io(interconnect, io_conn["in"], io_conn["out"], io_sides) + connect_io(interconnect, io_conn["in"], io_conn["out"], io_sides, give_north_io_sbs) # insert pipeline register if pipeline_reg is None: @@ -181,22 +188,32 @@ def create_uniform_interconnect(width: int, return interconnect - +# This function connects tiles that are at the edge to nearby tiles in the +# appropriate direction (North, west, east, or south neighbor). Makes this +# connection in the interconnect graph. Actual magma connection is done in +# a separate pass. def connect_io(interconnect: InterconnectGraph, input_port_conn: Dict[str, List[int]], output_port_conn: Dict[str, List[int]], - io_sides: List[IOSide]): + io_sides: List[IOSide], + give_north_io_sbs: bool = False): """connect tiles on the side""" if IOSide.None_ in io_sides: return width, height = interconnect.get_size() x_min, x_max, y_min, y_max = get_array_size(width, height, io_sides) + + interconnect_x_min = x_min + interconnect_x_max = x_max + interconnect_y_min = y_min-1 if give_north_io_sbs else y_min + interconnect_y_max = y_max + # compute tiles and sides for x in range(width): for y in range(height): - if x in range(x_min, x_max + 1) and \ - y in range(y_min, y_max + 1): + if x in range(interconnect_x_min, interconnect_x_max + 1) and \ + y in range(interconnect_y_min, interconnect_y_max + 1): continue # make sure that these margins tiles have empty switch boxes From c0299ea2d7590b68061f12cd685e4d1f5fe0e557 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 6 Dec 2024 16:11:20 -0800 Subject: [PATCH 2/5] Produce lopsided CGRA (with hacks) --- canal/interconnect.py | 1 - canal/util.py | 68 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/canal/interconnect.py b/canal/interconnect.py index 944ca58..e8d0260 100644 --- a/canal/interconnect.py +++ b/canal/interconnect.py @@ -275,7 +275,6 @@ def skip_margin_connection(self, tile): else: return tile.switchbox.num_track > 0 - # FIXME: This is a hack. Why are io2f and f2io tiles ports in the first place? They shouldn't be... def dont_lift_port(self, tile, port_name): # dont lift f2io and io2f ports to interconnect level since these connect to the SB within the I/O tile return self.give_north_io_sbs and (tile.y == 0 and ("f2io" in port_name or "io2f" in port_name)) diff --git a/canal/util.py b/canal/util.py index 78881a4..05cda5c 100644 --- a/canal/util.py +++ b/canal/util.py @@ -37,7 +37,9 @@ def compute_num_tracks(x_offset: int, y_offset: int, def get_array_size(width, height, io_sides): - x_min = 1 if IOSide.West in io_sides else 0 + # MO Hack + x_min = 0 + #x_min = 1 if IOSide.West in io_sides else 0 x_max = width - 2 if IOSide.East in io_sides else width - 1 y_min = 1 if IOSide.North in io_sides else 0 y_max = height - 2 if IOSide.South in io_sides else height - 1 @@ -98,7 +100,9 @@ def create_uniform_interconnect(width: int, io_sides = [IOSide.None_] x_min, x_max, y_min, y_max = get_array_size(width, height, io_sides) - interconnect_x_min = x_min + + # MO: Hack + interconnect_x_min = 4 interconnect_x_max = x_max interconnect_y_min = y_min-1 if give_north_io_sbs else y_min interconnect_y_max = y_max @@ -132,6 +136,44 @@ def create_uniform_interconnect(width: int, tile_circuit.add_additional_core(additional_core_interface, CoreConnectionType.SB | CoreConnectionType.CB) + # Handle North I/O if giving north I/O SB, since some may have been skipped above + if give_north_io_sbs: + for x in range(x_min, x_max + 1): + for y in range(y_min): + # skip if the tiles is already created + tile = interconnect.get_tile(x, y) + if tile is not None: + continue + # compute the number of tracks + num_track = compute_num_tracks(x_min, y_min, + x, y, track_info) + # create switch based on the type passed in + if sb_type == SwitchBoxType.Disjoint: + sb = DisjointSwitchBox(x, y, num_track, track_width) + elif sb_type == SwitchBoxType.Wilton: + sb = WiltonSwitchBox(x, y, num_track, track_width) + elif sb_type == SwitchBoxType.Imran: + sb = ImranSwitchBox(x, y, num_track, track_width) + else: + raise NotImplementedError(sb_type) + tile_circuit = Tile(x, y, track_width, sb, tile_height) + + interconnect.add_tile(tile_circuit) + core = column_core_fn(x, y) + + core_interface = CoreInterface(core) + interconnect.set_core(x, y, core_interface) + + additional_core = additional_core_fn(x, y) + if additional_core is not None: + additional_core_interface = CoreInterface(additional_core) + tile_circuit.add_additional_core(additional_core_interface, + CoreConnectionType.SB | CoreConnectionType.CB) + + + + + # create tiles without SB for x in range(width): for y in range(height): @@ -204,7 +246,9 @@ def connect_io(interconnect: InterconnectGraph, width, height = interconnect.get_size() x_min, x_max, y_min, y_max = get_array_size(width, height, io_sides) - interconnect_x_min = x_min + # MO: Hack + #interconnect_x_min = x_min + interconnect_x_min = 4 interconnect_x_max = x_max interconnect_y_min = y_min-1 if give_north_io_sbs else y_min interconnect_y_max = y_max @@ -221,23 +265,29 @@ def connect_io(interconnect: InterconnectGraph, if tile.core.core is None: continue - assert tile.switchbox.num_track == 0 + #breakpoint() + + # This means the tile isn't an I/O that needs to be connected using this function + if tile.switchbox.num_track > 0: + continue + + print(f"X: {x}, Y: {y}") + #assert tile.switchbox.num_track == 0 # compute the nearby tile - if x in range(0, x_min): + if x in range(0, interconnect_x_min): next_tile = interconnect[(x + 1, y)] side = SwitchBoxSide.WEST - elif x in range(x_max + 1, width): + elif x in range(interconnect_x_max + 1, width): next_tile = interconnect[(x - 1, y)] side = SwitchBoxSide.EAST - elif y in range(0, y_min): + elif y in range(0, interconnect_y_min): next_tile = interconnect[(x, y + 1)] side = SwitchBoxSide.NORTH else: - assert y in range(y_max + 1, height) + assert y in range(interconnect_y_max + 1, height) next_tile = interconnect[(x, y - 1)] side = SwitchBoxSide.SOUTH for input_port, conn in input_port_conn.items(): - #breakpoint() # input is from fabric to IO if input_port in tile.ports: port_node = tile.ports[input_port] From e4519748a276db4ba8ded8c37cf25dedf51695c3 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 6 Dec 2024 17:58:40 -0800 Subject: [PATCH 3/5] Working lopsided with hacks still --- canal/util.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/canal/util.py b/canal/util.py index 05cda5c..8fae387 100644 --- a/canal/util.py +++ b/canal/util.py @@ -37,9 +37,7 @@ def compute_num_tracks(x_offset: int, y_offset: int, def get_array_size(width, height, io_sides): - # MO Hack - x_min = 0 - #x_min = 1 if IOSide.West in io_sides else 0 + x_min = 1 if IOSide.West in io_sides else 0 x_max = width - 2 if IOSide.East in io_sides else width - 1 y_min = 1 if IOSide.North in io_sides else 0 y_max = height - 2 if IOSide.South in io_sides else height - 1 @@ -207,11 +205,21 @@ def create_uniform_interconnect(width: int, current_track = 0 for track_len in track_lens: for _ in range(track_info[track_len]): + + # This function connects neighboring switchboxes to each other (North, south east, west) + # MO: HACK: try doing in two passes interconnect.connect_switchbox(interconnect_x_min, interconnect_y_min, interconnect_x_max, interconnect_y_max, track_len, current_track, InterconnectPolicy.Ignore) + + # For unconnected I/O tiles + interconnect.connect_switchbox(x_min, interconnect_y_min, interconnect_x_max, + interconnect_y_min, + track_len, + current_track, + InterconnectPolicy.Ignore) current_track += 1 # insert io From 01d283b7d5af68d5a5675268fbc7e32552be03cc Mon Sep 17 00:00:00 2001 From: root Date: Mon, 9 Dec 2024 10:15:11 -0800 Subject: [PATCH 4/5] Remove fabric cols: checkpoint 1 --- canal/util.py | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/canal/util.py b/canal/util.py index 8fae387..e903164 100644 --- a/canal/util.py +++ b/canal/util.py @@ -59,6 +59,7 @@ def create_uniform_interconnect(width: int, io_sides: List[IOSide] = [IOSide.None_], io_conn: Dict[str, Dict[str, List[int]]] = None, give_north_io_sbs: bool = False, + num_fabric_cols_removed: int = 0, additional_core_fn: Callable[[int, int], Core] = lambda _, __: None, inter_core_connection: Dict[str, List[str]] = None ) -> InterconnectGraph: @@ -98,9 +99,7 @@ def create_uniform_interconnect(width: int, io_sides = [IOSide.None_] x_min, x_max, y_min, y_max = get_array_size(width, height, io_sides) - - # MO: Hack - interconnect_x_min = 4 + interconnect_x_min = num_fabric_cols_removed if num_fabric_cols_removed > 0 else x_min interconnect_x_max = x_max interconnect_y_min = y_min-1 if give_north_io_sbs else y_min interconnect_y_max = y_max @@ -134,7 +133,7 @@ def create_uniform_interconnect(width: int, tile_circuit.add_additional_core(additional_core_interface, CoreConnectionType.SB | CoreConnectionType.CB) - # Handle North I/O if giving north I/O SB, since some may have been skipped above + # Handle North I/O if giving north I/O SB, since some may have been skipped above due to num_fabric_cols_removed if give_north_io_sbs: for x in range(x_min, x_max + 1): for y in range(y_min): @@ -168,10 +167,6 @@ def create_uniform_interconnect(width: int, tile_circuit.add_additional_core(additional_core_interface, CoreConnectionType.SB | CoreConnectionType.CB) - - - - # create tiles without SB for x in range(width): for y in range(height): @@ -207,23 +202,24 @@ def create_uniform_interconnect(width: int, for _ in range(track_info[track_len]): # This function connects neighboring switchboxes to each other (North, south east, west) - # MO: HACK: try doing in two passes + # Pass 1: Contiguous tile array fabric interconnect.connect_switchbox(interconnect_x_min, interconnect_y_min, interconnect_x_max, interconnect_y_max, track_len, current_track, InterconnectPolicy.Ignore) - # For unconnected I/O tiles - interconnect.connect_switchbox(x_min, interconnect_y_min, interconnect_x_max, - interconnect_y_min, - track_len, - current_track, - InterconnectPolicy.Ignore) + # (Optional) Pass 2: For any unconnected I/O tiles due to "num_fabric_cols_removed" > 0 + if give_north_io_sbs and num_fabric_cols_removed > 0: + interconnect.connect_switchbox(x_min, interconnect_y_min, interconnect_x_max, + interconnect_y_min, + track_len, + current_track, + InterconnectPolicy.Ignore) current_track += 1 # insert io - connect_io(interconnect, io_conn["in"], io_conn["out"], io_sides, give_north_io_sbs) + connect_io(interconnect, io_conn["in"], io_conn["out"], io_sides, give_north_io_sbs, num_fabric_cols_removed) # insert pipeline register if pipeline_reg is None: @@ -246,7 +242,8 @@ def connect_io(interconnect: InterconnectGraph, input_port_conn: Dict[str, List[int]], output_port_conn: Dict[str, List[int]], io_sides: List[IOSide], - give_north_io_sbs: bool = False): + give_north_io_sbs: bool = False, + num_fabric_cols_removed: int = 0): """connect tiles on the side""" if IOSide.None_ in io_sides: return @@ -254,9 +251,7 @@ def connect_io(interconnect: InterconnectGraph, width, height = interconnect.get_size() x_min, x_max, y_min, y_max = get_array_size(width, height, io_sides) - # MO: Hack - #interconnect_x_min = x_min - interconnect_x_min = 4 + interconnect_x_min = num_fabric_cols_removed if num_fabric_cols_removed > 0 else x_min interconnect_x_max = x_max interconnect_y_min = y_min-1 if give_north_io_sbs else y_min interconnect_y_max = y_max @@ -273,14 +268,12 @@ def connect_io(interconnect: InterconnectGraph, if tile.core.core is None: continue - #breakpoint() # This means the tile isn't an I/O that needs to be connected using this function if tile.switchbox.num_track > 0: continue - - print(f"X: {x}, Y: {y}") #assert tile.switchbox.num_track == 0 + # compute the nearby tile if x in range(0, interconnect_x_min): next_tile = interconnect[(x + 1, y)] From f201df6b0aaaf931e0d2ecc8d76c781f4c708c91 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 10 Dec 2024 10:39:23 -0800 Subject: [PATCH 5/5] Remove breakpoint from code --- canal/interconnect.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/canal/interconnect.py b/canal/interconnect.py index e8d0260..491f6fa 100644 --- a/canal/interconnect.py +++ b/canal/interconnect.py @@ -715,8 +715,6 @@ def parse_node(self, node_str): elif node_str[0] == "REG": reg_name, track, x, y, bit_width = node_str[1:] graph = self.get_graph(bit_width) - if reg_name not in graph.get_tile(x, y).switchbox.registers: - breakpoint() return graph.get_tile(x, y).switchbox.registers[reg_name] elif node_str[0] == "RMUX": rmux_name, x, y, bit_width = node_str[1:]