Exemple #1
0
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")),
        ],
    )
Exemple #2
0
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])
Exemple #3
0
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)
Exemple #4
0
def instantiate_pe(row, col, right_edge=False, down_edge=False):
    """
    Instantiate the PE and all the registers connected to it.
    """
    # Add all the required cells.
    stdlib = py_ast.Stdlib()
    cells = [
        py_ast.Cell(py_ast.CompVar(f"pe_{row}_{col}"),
                    py_ast.CompInst(PE_NAME, [])),
        py_ast.Cell(py_ast.CompVar(f"top_{row}_{col}"),
                    stdlib.register(BITWIDTH)),
        py_ast.Cell(py_ast.CompVar(f"left_{row}_{col}"),
                    stdlib.register(BITWIDTH)),
    ]
    return cells
Exemple #5
0
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
Exemple #6
0
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
Exemple #7
0
#!/usr/bin/env python3

import numpy as np
from calyx import py_ast
from calyx.utils import bits_needed

# Global constant for the current bitwidth.
BITWIDTH = 32
# Name of the ouput array
OUT_MEM = py_ast.CompVar("out_mem")
PE_NAME = "mac_pe"

# Eventually, PE_DEF will be included a separate `.futil` file.
PE_DEF = """
component mac_pe(top: 32, left: 32) -> (out: 32) {
  cells {
    // Storage
    acc = std_reg(32);
    // Computation
    add = std_add(32);
    mul = std_mult_pipe(32);
  }
  wires {
    group do_add {
      add.left = acc.out;
      add.right = mul.out;
      acc.in = add.out;
      acc.write_en = 1'd1;
      do_add[done] = acc.done;
    }
    out = acc.out;