Skip to content

Commit

Permalink
Merge pull request #63 from StanfordAHA/hetero-cgra
Browse files Browse the repository at this point in the history
Hetero cgra lopsided layout
  • Loading branch information
mcoduoza authored Dec 11, 2024
2 parents 420d831 + f201df6 commit 8e69610
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 23 deletions.
41 changes: 40 additions & 1 deletion canal/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
31 changes: 24 additions & 7 deletions canal/interconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -264,15 +268,29 @@ 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

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
Expand Down Expand Up @@ -697,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:]
Expand All @@ -719,7 +735,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):
Expand Down
98 changes: 83 additions & 15 deletions canal/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ 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,
num_fabric_cols_removed: int = 0,
additional_core_fn: Callable[[int, int], Core] = lambda _, __: None,
inter_core_connection: Dict[str, List[str]] = None
) -> InterconnectGraph:
Expand Down Expand Up @@ -96,9 +98,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 = 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

# 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)
Expand All @@ -125,6 +133,40 @@ 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 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):
# 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):
Expand Down Expand Up @@ -158,15 +200,26 @@ 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,

# This function connects neighboring switchboxes to each other (North, south east, west)
# 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)

# (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)
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:
Expand All @@ -181,46 +234,61 @@ 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,
num_fabric_cols_removed: int = 0):
"""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 = 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

# 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
tile = interconnect[(x, y)]

if tile.core.core is None:
continue
assert tile.switchbox.num_track == 0

# This means the tile isn't an I/O that needs to be connected using this function
if tile.switchbox.num_track > 0:
continue
#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]
Expand Down

0 comments on commit 8e69610

Please sign in to comment.