Exemplo n.º 1
0
 def __init__(self, obj):
     self.cat = {
         'istream': Instance.Arg.Cat.ISTREAM,
         'ostream': Instance.Arg.Cat.OSTREAM,
         'scalar': Instance.Arg.Cat.SCALAR,
         'mmap': Instance.Arg.Cat.MMAP,
         'async_mmap': Instance.Arg.Cat.ASYNC_MMAP,
     }[obj['cat']]
     self.name = rtl.sanitize_array_name(obj['name'])
     self.ctype = obj['type']
     self.width = obj['width']
Exemplo n.º 2
0
 def __init__(self, task: 'Task', instance_id: int, **kwargs):
     self.task = task
     self.instance_id = instance_id
     self.step = kwargs.pop('step')
     self.args: Tuple[Instance.Arg, ...] = tuple(
         sorted(
             Instance.Arg(
                 name=rtl.sanitize_array_name(arg['arg']),
                 instance=self,
                 cat=arg['cat'],
                 port=port,
                 is_upper=task.is_upper,
             ) for port, arg in kwargs.pop('args').items()))
Exemplo n.º 3
0
def generate_floorplan(
    top_task: Task,
    work_dir: str,
    fifo_width_getter: Callable[[Task, str], int],
    connectivity_fp: Optional[TextIO],
    part_num: str,
    **kwargs,
) -> Dict[str, Any]:
    # pylint: disable=import-outside-toplevel
    import autobridge.HLSParser.tapa as autobridge

    _logger.info('total area estimation: %s', top_task.total_area)

    vertices = {x.name: x.task.name for x in top_task.instances}
    edges = {}

    # Self area for top task represents mainly the control_s_axi instance.
    area = {top_task.name: make_autobridge_area(top_task.self_area)}
    for instance in top_task.instances:
        if instance.task.name not in area:
            # Total area handles hierarchical designs for AutoBridge.
            task_area = make_autobridge_area(instance.task.total_area)
            area[instance.task.name] = task_area
            _logger.debug('area of %s: %s', instance.task.name, task_area)

    config = {
        'CompiledBy': 'TAPA',
        'Board': part_num.split('-')[0][2:].upper(),
        'OptionalFloorplan': collections.defaultdict(list),
        'Vertices': vertices,
        'Edges': edges,
        'Area': area,
        'ExtraKeyWordArgs': kwargs,
    }

    # Remember the number of ports instantiated (for shell area estimation).
    region_to_shell_area = collections.defaultdict(dict)

    # Set floorplans for tasks connected to MMAP and the ctrl instance.
    vertices[rtl.ctrl_instance_name(top_task.name)] = top_task.name
    ddr_list: List[int] = []
    for connectivity in parse_connectivity(connectivity_fp):
        if not connectivity:
            continue
        dot = connectivity.find('.')
        colon = connectivity.find(':')
        kernel = connectivity[:dot]
        kernel_arg = connectivity[dot + 1:colon]
        port = connectivity[colon + 1:]
        region = get_port_region(part_num, port=port)
        port_cat, port_id = parse_port(port)
        if port_cat == 'DDR':
            ddr_list.append(port_id)
        elif port_cat == 'HBM':
            accumulate_area(region_to_shell_area[region], AREA_PER_HBM)
        if not region:
            continue
        for arg in top_task.args[kernel_arg]:
            _logger.debug('%s is constrained to %s because of %s',
                          arg.instance.name, region, arg.name)
            config['OptionalFloorplan'][region].append(arg.instance.name)
            if arg.cat == Instance.Arg.Cat.ASYNC_MMAP:
                config['OptionalFloorplan'][region].append(
                    rtl.async_mmap_instance_name(arg.mmap_name))
    config['DDR'] = list(dict.fromkeys(ddr_list))

    # Account for area used for the shell platform and memory subsystem.
    if config['Board'] == 'U280':
        accumulate_area(
            region_to_shell_area[get_ctrl_instance_region(part_num)],
            AREA_OF_PLATFORM,
        )
    for region, shell_area in region_to_shell_area.items():
        placeholder_name = _PLACEHOLDER_REGION_PREFIX + region
        # Each placeholder is both a task name and a task instance name.
        vertices[placeholder_name] = placeholder_name
        # Placeholders are created per region.
        accumulate_area(area.setdefault(placeholder_name, {}), shell_area)
        # Placeholders are bound to their own region.
        config['OptionalFloorplan'][region].append(placeholder_name)

    width_table = {port.name: port.width for port in top_task.ports.values()}

    # ASYNC_MMAP is outside of its instantiating module and needs its own
    # floorplanning constraint.
    for instance in top_task.instances:
        for arg in instance.args:
            if arg.cat == Instance.Arg.Cat.ASYNC_MMAP:
                floorplan_vertex_name = rtl.async_mmap_instance_name(
                    arg.mmap_name)
                vertices[floorplan_vertex_name] = floorplan_vertex_name
                # TODO: use a better calibrated model
                width = max(width_table[arg.name], 32)
                area[floorplan_vertex_name] = AREA_OF_M_AXI[width]

    # Generate edges for scalar connections from the ctrl instance.
    for port in top_task.ports.values():
        width = port.width
        if port.cat in {Instance.Arg.Cat.MMAP, Instance.Arg.Cat.ASYNC_MMAP}:
            width = 64
        for arg in top_task.args[port.name]:
            edges[arg.instance.get_instance_arg(port.name)] = {
                'produced_by': rtl.ctrl_instance_name(top_task.name),
                'consumed_by': arg.instance.name,
                'width': width,
                'depth': 1,
            }

    # Generate edges for FIFOs instantiated in the top task.
    for fifo_name, fifo in top_task.fifos.items():
        edges[rtl.sanitize_array_name(fifo_name)] = {
            'produced_by': util.get_instance_name(fifo['produced_by']),
            'consumed_by': util.get_instance_name(fifo['consumed_by']),
            'width': fifo_width_getter(top_task, fifo_name),
            'depth': top_task.fifos[fifo_name]['depth']
        }

    # Dedup items.
    for region, instances in config['OptionalFloorplan'].items():
        instances[:] = dict.fromkeys(instances)
        region_area = {}
        for instance in instances:
            accumulate_area(region_area, area[vertices[instance]])
        _logger.info('hard constraint area in %s: %s', region, region_area)

    # Dump the input to AutoBridge.
    with open(os.path.join(work_dir, 'autobridge_config.json'), 'w') as fp:
        json.dump(config, fp, indent=2)

    # Generate floorplan using AutoBridge.
    floorplan = autobridge.generate_constraints(config)

    # Remove placeholders.
    for pblock in floorplan:
        floorplan[pblock] = [
            x for x in floorplan[pblock]
            if not (isinstance(x, str)
                    and x.startswith(_PLACEHOLDER_REGION_PREFIX))
        ]

    # Dump the output from AutoBridge.
    with open(os.path.join(work_dir, 'autobridge_floorplan.json'), 'w') as fp:
        json.dump(floorplan, fp, indent=2)

    return floorplan
Exemplo n.º 4
0
Arquivo: core.py Projeto: Blaok/tapa
    def _process_partition_directive(
        self,
        directive: Dict[str, List[Union[str, Dict[str, List[str]]]]],
        constraints: TextIO,
    ) -> None:
        _logger.info('processing partition directive')

        # mapping instance names to region names
        instance_dict = {self.ctrl_instance_name: ''}

        topology: Dict[str, Dict[str, List[str]]] = {}

        # list of strings that creates the pblocks, may be empty
        pblock_tcl: List[str] = []

        for instance in self.top_task.instances:
            for arg in instance.args:
                if arg.cat == Instance.Arg.Cat.ASYNC_MMAP:
                    instance_dict[rtl.async_mmap_instance_name(
                        arg.mmap_name)] = ''
            instance_dict[instance.name] = ''

        # check that all non-FIFO instances are assigned to one and only one SLR
        program_instance_set = set(instance_dict)
        directive_instance_set: Set[str] = set()
        for region, instances in directive.items():
            subset: Set[str] = set()
            for instance_obj in instances:
                if isinstance(instance_obj, str):
                    instance_obj = rtl.sanitize_array_name(instance_obj)
                    instance_dict[instance_obj] = region
                    subset.add(instance_obj)
                else:
                    topology[region] = instance_obj
                    pblock_tcl.append(instance_obj.pop('tcl', ''))
            intersect = subset & directive_instance_set
            if intersect:
                raise InputError('more than one region assignment: %s' %
                                 intersect)
            directive_instance_set.update(subset)
        missing_instances = program_instance_set - directive_instance_set
        if missing_instances:
            raise InputError('missing region assignment: {%s}' %
                             ', '.join(missing_instances))
        unnecessary_instances = (directive_instance_set -
                                 program_instance_set - rtl.BUILTIN_INSTANCES)
        if unnecessary_instances:
            raise InputError('unnecessary region assignment: {%s}' %
                             ', '.join(unnecessary_instances))

        # determine partitioning of each FIFO
        fifo_partition_count: Dict[str, int] = {}

        for name, meta in self.top_task.fifos.items():
            name = rtl.sanitize_array_name(name)
            producer_region = instance_dict[util.get_instance_name(
                meta['produced_by'])]
            consumer_region = instance_dict[util.get_instance_name(
                meta['consumed_by'])]
            if producer_region == consumer_region:
                fifo_partition_count[name] = 1
                instance_dict[name] = producer_region
            else:
                if producer_region not in topology:
                    raise InputError(
                        f'missing topology info for {producer_region}')
                if consumer_region not in topology[producer_region]:
                    raise InputError(
                        f'{consumer_region} is not reachable from '
                        f'{producer_region}')
                idx = 0  # makes linter happy
                for idx, region in enumerate((
                        producer_region,
                        *topology[producer_region][consumer_region],
                        consumer_region,
                )):
                    instance_dict[rtl.fifo_partition_name(name, idx)] = region
                fifo_partition_count[name] = idx + 1

        rtl.print_constraints(instance_dict,
                              constraints,
                              pre=''.join(pblock_tcl))

        self.top_task.module.register_level = max(
            map(len,
                topology[instance_dict[self.ctrl_instance_name]].values()),
            default=-1,
        ) + 1
        _logger.info('top task register level set to %d based on floorplan',
                     self.top_task.module.register_level)
        self.top_task.module.fifo_partition_count = fifo_partition_count
Exemplo n.º 5
0
def generate_floorplan(
    top_task: Task,
    work_dir: str,
    fifo_width_getter: Callable[[Task, str], int],
    connectivity_fp: Optional[TextIO],
    part_num: str,
) -> Dict[str, Any]:
    # pylint: disable=import-outside-toplevel
    import autoparallel.FE.tapa.TapaManager as autobridge

    vertices = {x.name: x.task.name for x in top_task.instances}
    edges = {}
    config = {
        'CompiledBy': 'TAPA',
        'Board': part_num.split('-')[0][2:].upper(),
        'ProjectPath': work_dir,
        'OptionalFloorplan': collections.defaultdict(list),
        'Vertices': vertices,
        'Edges': edges,
    }

    # Set floorplans for tasks connected to MMAP and the ctrl instance.
    vertices[rtl.ctrl_instance_name(top_task.name)] = top_task.name
    for connectivity in parse_connectivity(connectivity_fp):
        dot = connectivity.find('.')
        colon = connectivity.find(':')
        kernel = connectivity[:dot]
        kernel_arg = connectivity[dot + 1:colon]
        region = get_port_region(part_num, port=connectivity[colon + 1:])
        if not region:
            continue
        for arg in top_task.args[kernel_arg]:
            _logger.debug('%s is constrained to %s because of %s',
                          arg.instance.name, region, arg.name)
            config['OptionalFloorplan'][region].append(arg.instance.name)
            if arg.cat == Instance.Arg.Cat.ASYNC_MMAP:
                config['OptionalFloorplan'][region].append(
                    rtl.async_mmap_instance_name(arg.mmap_name))

    # Dedup items.
    for region, instances in config['OptionalFloorplan'].items():
        instances[:] = list(dict.fromkeys(instances))

    # ASYNC_MMAP is outside of its instantiating module and needs its own
    # floorplanning constraint.
    for instance in top_task.instances:
        for arg in instance.args:
            if arg.cat == Instance.Arg.Cat.ASYNC_MMAP:
                floorplan_vertex_name = rtl.async_mmap_instance_name(
                    arg.mmap_name)
                vertices[floorplan_vertex_name] = floorplan_vertex_name

    # Generate edges for scalar connections from the ctrl instance.
    for port in top_task.ports.values():
        width = port.width
        if port.cat in {Instance.Arg.Cat.MMAP, Instance.Arg.Cat.ASYNC_MMAP}:
            width = 64
        for arg in top_task.args[port.name]:
            edges[arg.instance.get_instance_arg(port.name)] = {
                'produced_by': rtl.ctrl_instance_name(top_task.name),
                'consumed_by': arg.instance.name,
                'width': width,
            }

    # Generate edges for FIFOs instantiated in the top task.
    for fifo_name, fifo in top_task.fifos.items():
        edges[rtl.sanitize_array_name(fifo_name)] = {
            'produced_by': util.get_instance_name(fifo['produced_by']),
            'consumed_by': util.get_instance_name(fifo['consumed_by']),
            'width': fifo_width_getter(top_task, fifo_name),
        }

    # Dump the input to AutoBridge.
    with open(os.path.join(work_dir, 'autobridge_config.json'), 'w') as fp:
        json.dump(config, fp, indent=2)

    # Generate floorplan using AutoBridge.
    floorplan = autobridge.TapaManager(config).generateTAPAConstraints()

    # Dump the output from AutoBridge.
    with open(os.path.join(work_dir, 'autobridge_floorplan.json'), 'w') as fp:
        json.dump(floorplan, fp, indent=2)

    return floorplan