def name(self) -> str: return util.get_instance_name((self.task.name, self.instance_id))
def _instantiate_fifos(self, task: Task) -> None: _logger.debug(' instantiating FIFOs in %s', task.name) # skip instantiating if the fifo is not declared in this task fifos = { name: fifo for name, fifo in task.fifos.items() if 'depth' in fifo } if not fifos: return col_width = max( max(len(name), len(util.get_instance_name(fifo['consumed_by'])), len(util.get_instance_name(fifo['produced_by']))) for name, fifo in fifos.items()) for fifo_name, fifo in fifos.items(): _logger.debug(' instantiating %s.%s', task.name, fifo_name) # add FIFO instances task.module.add_fifo_instance( name=fifo_name, width=self._get_fifo_width(task, fifo_name), depth=fifo['depth'], ) # print debugging info debugging_blocks = [] fmtargs = { 'fifo_prefix': '\\033[97m', 'fifo_suffix': '\\033[0m', 'task_prefix': '\\033[90m', 'task_suffix': '\\033[0m', } for suffixes, fmt, fifo_tag in zip( (rtl.ISTREAM_SUFFIXES, rtl.OSTREAM_SUFFIXES), ('DEBUG: R: {fifo_prefix}{fifo:>{width}}{fifo_suffix} -> ' '{task_prefix}{task:<{width}}{task_suffix} %h', 'DEBUG: W: {task_prefix}{task:>{width}}{task_suffix} -> ' '{fifo_prefix}{fifo:<{width}}{fifo_suffix} %h'), ('consumed_by', 'produced_by')): display = ast.SingleStatement(statement=ast.SystemCall( syscall='display', args=(ast.StringConst(value=fmt.format( width=col_width, fifo=fifo_name, task=(util.get_instance_name(fifo[fifo_tag])), **fmtargs)), ast.Identifier( name=rtl.wire_name(fifo_name, suffixes[0]))))) debugging_blocks.append( ast.Always( sens_list=rtl.CLK_SENS_LIST, statement=ast.make_block( ast.IfStatement( cond=ast.Eq( left=ast.Identifier(name=rtl.wire_name( fifo_name, suffixes[-1])), right=rtl.TRUE, ), true_statement=ast.make_block(display), false_statement=None)))) task.module.add_logics(debugging_blocks)
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
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
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