def instantiate_indexor(prefix, width): """ Instantiate an indexor for accessing memory with name `prefix`. Generates structure to initialize and update the indexor. The initializor starts sets the memories to their maximum value because we expect all indices to be incremented once before being used. Returns (cells, structure) """ stdlib = py_ast.Stdlib() name = py_ast.CompVar(NAME_SCHEME["index name"].format(prefix=prefix)) add_name = py_ast.CompVar(f"{prefix}_add") cells = [ py_ast.Cell(name, stdlib.register(width)), py_ast.Cell(add_name, stdlib.op("add", width, signed=False)), ] init_name = py_ast.CompVar(NAME_SCHEME["index init"].format(prefix=prefix)) init_group = py_ast.Group( init_name, connections=[ py_ast.Connect(py_ast.ConstantPort(width, 2**width - 1), py_ast.CompPort(name, "in")), py_ast.Connect(py_ast.ConstantPort(1, 1), py_ast.CompPort(name, "write_en")), py_ast.Connect(py_ast.CompPort(name, "done"), py_ast.HolePort(init_name, "done")), ], ) upd_name = py_ast.CompVar( NAME_SCHEME["index update"].format(prefix=prefix)) upd_group = py_ast.Group( upd_name, connections=[ py_ast.Connect(py_ast.ConstantPort(width, 1), py_ast.CompPort(add_name, "left")), py_ast.Connect(py_ast.CompPort(name, "out"), py_ast.CompPort(add_name, "right")), py_ast.Connect(py_ast.CompPort(add_name, "out"), py_ast.CompPort(name, "in")), py_ast.Connect(py_ast.ConstantPort(1, 1), py_ast.CompPort(name, "write_en")), py_ast.Connect(py_ast.CompPort(name, "done"), py_ast.HolePort(upd_name, "done")), ], ) return (cells, [init_group, upd_group])
def instantiate_data_move(row, col, right_edge, down_edge): """ Generates groups for "data movers" which are groups that move data from the `write` register of the PE at (row, col) to the read register of the PEs at (row+1, col) and (row, col+1) """ name = f"pe_{row}_{col}" structures = [] if not right_edge: group_name = py_ast.CompVar( NAME_SCHEME["register move right"].format(pe=name)) src_reg = py_ast.CompVar(f"left_{row}_{col}") dst_reg = py_ast.CompVar(f"left_{row}_{col + 1}") mover = py_ast.Group( group_name, connections=[ py_ast.Connect(py_ast.CompPort(src_reg, "out"), py_ast.CompPort(dst_reg, "in")), py_ast.Connect(py_ast.ConstantPort(1, 1), py_ast.CompPort(dst_reg, "write_en")), py_ast.Connect( py_ast.CompPort(dst_reg, "done"), py_ast.HolePort(group_name, "done"), ), ], ) structures.append(mover) if not down_edge: group_name = py_ast.CompVar( NAME_SCHEME["register move down"].format(pe=name)) src_reg = py_ast.CompVar(f"top_{row}_{col}") dst_reg = py_ast.CompVar(f"top_{row + 1}_{col}") mover = py_ast.Group( group_name, connections=[ py_ast.Connect(py_ast.CompPort(src_reg, "out"), py_ast.CompPort(dst_reg, "in")), py_ast.Connect(py_ast.ConstantPort(1, 1), py_ast.CompPort(dst_reg, "write_en")), py_ast.Connect( py_ast.CompPort(dst_reg, "done"), py_ast.HolePort(group_name, "done"), ), ], ) structures.append(mover) return structures
def instantiate_output_move(row, col, row_idx_bitwidth, col_idx_bitwidth): """ Generates groups to move the final value from a PE into the output array. """ group_name = py_ast.CompVar( NAME_SCHEME["out mem move"].format(pe=f"pe_{row}_{col}")) pe = py_ast.CompVar(f"pe_{row}_{col}") return py_ast.Group( group_name, connections=[ py_ast.Connect( py_ast.ConstantPort(row_idx_bitwidth, row), py_ast.CompPort(OUT_MEM, "addr0"), ), py_ast.Connect( py_ast.ConstantPort(col_idx_bitwidth, col), py_ast.CompPort(OUT_MEM, "addr1"), ), py_ast.Connect(py_ast.CompPort(pe, "out"), py_ast.CompPort(OUT_MEM, "write_data")), py_ast.Connect(py_ast.ConstantPort(1, 1), py_ast.CompPort(OUT_MEM, "write_en")), py_ast.Connect(py_ast.CompPort(OUT_MEM, "done"), py_ast.HolePort(group_name, "done")), ], )
def instantiate_memory(top_or_left, idx, size): """ Instantiates: - top memory - structure to move data from memory to read registers. Returns (cells, structure) tuple. """ if top_or_left == "top": name = f"t{idx}" target_reg = f"top_0_{idx}" elif top_or_left == "left": name = f"l{idx}" target_reg = f"left_{idx}_0" else: raise f"Invalid top_or_left: {top_or_left}" var_name = py_ast.CompVar(f"{name}") idx_name = py_ast.CompVar(NAME_SCHEME["index name"].format(prefix=name)) group_name = py_ast.CompVar(NAME_SCHEME["memory move"].format(prefix=name)) target_reg = py_ast.CompVar(target_reg) structure = py_ast.Group( group_name, connections=[ py_ast.Connect(py_ast.CompPort(idx_name, "out"), py_ast.CompPort(var_name, "addr0")), py_ast.Connect( py_ast.CompPort(var_name, "read_data"), py_ast.CompPort(target_reg, "in"), ), py_ast.Connect(py_ast.ConstantPort(1, 1), py_ast.CompPort(target_reg, "write_en")), py_ast.Connect(py_ast.CompPort(target_reg, "done"), py_ast.HolePort(group_name, "done")), ], ) idx_width = bits_needed(size) # Instantiate the indexor (idx_cells, idx_structure) = instantiate_indexor(name, idx_width) idx_structure.append(structure) # Instantiate the memory idx_cells.append( py_ast.Cell( var_name, py_ast.Stdlib().mem_d1(BITWIDTH, size, idx_width), is_external=True, )) return (idx_cells, idx_structure)
def generate_control(top_length, top_depth, left_length, left_depth, gen_metadata=False): """ Logically, control performs the following actions: 1. Initialize all the memory indexors at the start. 2. For each time step in the schedule: a. Move the data required by PEs in this cycle. b. Update the memory indices if needed. c. Run the PEs that need to be active this cycle. """ sch = schedule_to_timesteps( generate_schedule(top_length, top_depth, left_length, left_depth)) control = [] # Initialize all memories. init_indices = [ py_ast.Enable(NAME_SCHEME["index init"].format(prefix=f"t{idx}")) for idx in range(top_length) ] init_indices.extend([ py_ast.Enable(NAME_SCHEME["index init"].format(prefix=f"l{idx}")) for idx in range(left_length) ]) control.append(py_ast.ParComp(init_indices)) # source_pos metadata init init_tag = 0 source_map = {} def counter(): nonlocal init_tag old = init_tag init_tag += 1 return old # end source pos init # Increment memories for PE_00 before computing with it. upd_pe00_mem = [] upd_pe00_mem.append( py_ast.Enable(NAME_SCHEME["index update"].format(prefix="t0"))) upd_pe00_mem.append( py_ast.Enable(NAME_SCHEME["index update"].format(prefix="l0"))) control.append(py_ast.ParComp(upd_pe00_mem)) for (idx, elements) in enumerate(sch): # Move all the requisite data. move = [py_ast.Enable(row_data_mover_at(r, c)) for (r, c) in elements] move.extend( [py_ast.Enable(col_data_mover_at(r, c)) for (r, c) in elements]) control.append(py_ast.ParComp(move)) # Update the indices if needed. more_control = [] if idx < len(sch) - 1: next_elements = sch[idx + 1] upd_memory = [ py_ast.Enable(upd) for (r, c) in next_elements if (r == 0 or c == 0) for upd in index_update_at(r, c) ] more_control.extend(upd_memory) # py_ast.Invoke the PEs and move the data to the next layer. for (r, c) in elements: invoke = py_ast.Invoke( id=py_ast.CompVar(f"pe_{r}_{c}"), in_connects=[ ("top", py_ast.CompPort(py_ast.CompVar(f"top_{r}_{c}"), "out")), ( "left", py_ast.CompPort(py_ast.CompVar(f"left_{r}_{c}"), "out"), ), ], out_connects=[], ) if gen_metadata: tag = counter() more_control.append(invoke.with_attr("pos", tag)) source_map[tag] = f"pe_{r}_{c} running. Iteration {idx}" else: more_control.append(invoke) control.append(py_ast.ParComp(more_control)) # Move all the results into output memory mover_groups = [] for row in range(left_length): for col in range(top_length): mover_groups.append( py_ast.Enable( NAME_SCHEME["out mem move"].format(pe=f"pe_{row}_{col}"))) control.append(py_ast.SeqComp(mover_groups)) return py_ast.SeqComp(stmts=control), source_map