def start(self, tag, attrs, nsmap=None): if self.filectx is None: self.filectx = self.xmlfile.__enter__( ) # we have to use context manager manually while tag == 'block' and len(self.stack) == 1: mapped = io_bindings.get(attrs['name']) if mapped is None: break x, y, subblock = mapped tile = get_hierarchical_tile(self.context.top, (x, y)) if tile is None: break tile = tile[-1].model if not tile.block.module_class.is_io_block or subblock < 0 or subblock >= tile.block.capacity: raise PRGAAPIError( "No IO block found at position ({}, {}, {})".format( x, y, subblock)) matched = _reprog_instance.match(attrs['instance']) old_tile = context.tiles[matched.group('name')] if old_tile.block is not tile.block: raise PRGAAPIError( "Tile '{}' at position ({}, {}) is not compatible with tile '{}'" .format(tile.name, x, y, old_tile.name)) attrs['instance'] = tile.name + matched.group('index') self.remapping = (old_tile.name, tile.name) break ctx = self.filectx.element(tag, attrs) self.stack.append((tag, ctx)) ctx.__enter__()
def create_global(self, name, width=1, is_clock=False, bind_to_position=None, bind_to_subblock=None): """Create a global wire. Args: name (:obj:`str`): Name of the global wire width (:obj:`int`): Number of bits in the global wire is_clock (:obj:`bool`): If this global wire is a clock. A global clock must be 1-bit wide bind_to_position (:obj:`Position`): Assign the IOB at the position as the driver of this global wire. If not specified, use `Global.bind` to bind later bind_to_subblock (:obj:`int`): Assign the IOB with the sub-block ID as the driver of this global wire. If ``bind_to_position`` is specified, ``bind_to_subblock`` is ``0`` by default Returns: `Global`: The created global wire """ if name in self._globals: raise PRGAAPIError( "Global wire named '{}' is already created".format(name)) elif width != 1: raise PRGAAPIError( "Only 1-bit wide global wires are supported now") global_ = self._globals.setdefault(name, Global(name, width, is_clock)) if bind_to_position is not None: global_.bind(bind_to_position, uno(bind_to_subblock, 0)) return global_
def find_verilog_top(files, top = None): """Find and parse the top-level module in a list of Verilog files. Args: files (:obj:`Sequence` [:obj:`str` ]): Verilog files top (:obj:`str`): the name of the top-level module if there are more than one modules in the Verilog files Returns: `VerilogModule`: """ mods = {x.name : x for f in files for x in Vex().extract_objects(f)} mod = next(iter(itervalues(mods))) if len(mods) > 1: if top is not None: try: mod = mods[top] except KeyError: raise PRGAAPIError("Module '{}' is not found in the file(s)") else: raise PRGAAPIError('Multiple modules found in the file(s) but no top is specified') ports = {} for port in mod.ports: matched = _reprog_width.match(port.data_type) direction = port.mode.strip() if direction == 'input': direction = PortDirection.input_ elif direction == 'output': direction = PortDirection.output else: raise PRGAAPIError("Unknown port direction '{}'".format(direction)) low, high = None, None matched = _reprog_width.match(port.data_type) if matched is not None: start, end = map(int, matched.group('start', 'end')) if start > end: low, high = end, start + 1 elif end > start: low, high = start, end + 1 else: low, high = start, start + 1 ports[port.name] = VerilogPort(port.name, direction, low, high) return VerilogModule(mod.name, ports)
def create_cluster(self, name): """Create a cluster. Args: name (:obj:`str`): Name of the cluster Returns: `Cluster`: The created cluster """ if name in self._modules: raise PRGAAPIError("Module '{}' is already created".format(name)) return self._modules.setdefault(name, Cluster(name))
def create_io_block(self, name, capacity, input_=True, output=True): """Create an IO block. Args: name (:obj:`str`): Name of the IO block capacity (:obj:`int`): Number of block instances in one tile input_ (:obj:`bool`): If set, the IOB can be used as an external input output (:obj:`bool`): If set, the IOB can be used as an external output Returns: `IOBlock`: The created IO block """ if name in self._modules: raise PRGAAPIError("Module '{}' is already created".format(name)) io_primitive = ((self.primitives['iopad'] if output else self.primitives['inpad']) if input_ else (self.primitives['outpad'] if output else None)) if io_primitive is None: raise PRGAAPIError( "At least one of 'input' and 'output' must be True") return self._modules.setdefault(name, IOBlock(name, io_primitive, capacity))
def create_direct_tunnel(self, name, source, sink, offset): """Create a direct inter-block tunnel. Args: name (:obj:`str`): Name of this direct inter-block tunnel source (`AbstractBlockPort`): Source of the tunnel sink (`AbstractBlockPort`): Sink of the tunnel offset (:obj:`tuple` [:obj:`int`, :obj:`int` ]): Position of the source block relative to the sink block. This definition is the opposite of how VPR defines a ``<direct>`` tag. In addition, ``offset`` is defined based on the position of the blocks, not the ports """ if name in self._directs: raise PRGAAPIError( "Direct inter-block tunnel named '{}' is already created". format(name)) elif not (source.parent.module_class.is_logic_block and sink.parent.module_class.is_logic_block): raise PRGAAPIError( "Direct inter-block tunnel can only be created between logic block ports" ) return self._directs.setdefault( name, DirectTunnel(name, source, sink, Position(*offset)))
def create_tile(self, name, block, orientation=Orientation.auto): """Create a tile. Args: name (:obj:`str`): Name of the tile block (`IOBlock` or `LogicBlock`): Block in this tile orientation (`Orientation`): On which side of the top-level array should the tile be placed on. Required and only used if ``block`` is an `IOBlock` Returns: `Tile`: The created tile Note: Sadly, unlike VPR, each tile must have a unique name DIFFERENT from the name of the block """ if name in self._modules: raise PRGAAPIError("Module '{}' is already created".format(name)) elif block.module_class.is_io_block and orientation in ( None, Orientation.auto): raise PRGAAPIError( "Non-auto 'orientation' are required since '{}' is an IO block" .format(block)) return self._modules.setdefault(name, Tile(name, block, orientation))
def create_logic_block(self, name, width=1, height=1): """Create a logic block. Args: name (:obj:`str`): Name of the logic block width (:obj:`int`): Width of the logic block in terms of tiles height (:obj:`int`): Height of the logic block in terms of tiles Returns: `LogicBlock`: The created logic block """ if name in self._modules: raise PRGAAPIError("Module '{}' is already created".format(name)) return self._modules.setdefault(name, LogicBlock(name, width, height))
def create_segment(self, name, width, length): """Create a wire segment prototype. Args: name (:obj:`str`): Name of the segment width (:obj:`int`): Number of wire segments per routing channel, per direction. If the wire segments are longer than 1 tile, this is the number of wire segments originated from the channel length (:obj:`int`): Length of the wire segments, in terms of tiles Returns: `Segment`: The created segment prototype """ if name in self._segments: raise PRGAAPIError( "Wire segment named '{}' is already created".format(name)) return self._segments.setdefault(name, Segment(name, width, length))
def create_array(self, name, width, height, coverage=ChannelCoverage(), inner_coverage=ChannelCoverage(True, True, True, True)): """Create a (sub-)array. Args: name (:obj:`str`): Name of the array width (:obj:`int`): Number of tiles in the X axis height (:obj:`int`): Number of tiles in the Y axis coverage (`ChannelCoverage`): Coverage of routing channels surrounding the array. No routing channels are covered by default inner_coverage (`ChannelCoverage`): Coverage of the channels on the edges of this array. All edges are covered by default Returns: `Array`: The created array """ if name in self._modules: raise PRGAAPIError("Module '{}' is already created".format(name)) return self._modules.setdefault( name, Array(name, width, height, coverage, inner_coverage))
def iobind(context, mod_top, fixed = None): """Generate IO assignment. Args: context (`ArchitectureContext`): The architecture context of the custom FPGA mod_top (`VerilogModule`): Top-level module of target design fixed (:obj:`Mapping` [:obj:`str`, :obj:`tuple` [:obj:`int`, :obj:`int`, :obj:`int` ]]): Manually assigned IOs """ # prepare assignment map assignments = [[([], []) for _0 in range(context.top.height)] for _1 in range(context.top.width)] for x, y in product(range(context.top.width), range(context.top.height)): tile = get_hierarchical_tile(context.top, (x, y)) if tile is not None and hierarchical_position(tile) == (x, y): tile = tile[-1].model if tile.block.module_class.is_io_block: i, o = map(tile.block.physical_ports.get, ("exti", "exto")) if i is not None: i = [None] * tile.block.capacity else: i = [] if o is not None: o = [None] * tile.block.capacity else: o = [] assignments[x][y] = i, o # process fixed assignments processed = {} for name, (x, y, subblock) in iteritems(uno(fixed, {})): direction = PortDirection.input_ if name.startswith('out:'): name = name[4:] direction = PortDirection.output matched = _reprog_bit.match(name) port_name, index = matched.group('name', 'index') index = None if index is None else int(index) port = mod_top.ports.get(port_name) if port is None: raise PRGAAPIError("Port '{}' not found in module '{}'" .format(port_name, mod_top.name)) elif port.direction is not direction: raise PRGAAPIError("Direction mismatch: port '{}' is {} in behavioral model but {} in IO bindings" .format(port_name, port.direction.name, direction)) elif index is None and port.low is not None: raise PRGAAPIError("Port '{}' is a bus and requires an index" .format(port_name)) elif index is not None and (port.low is None or index < port.low or index >= port.high): raise PRGAAPIError("Bit index '{}' is not in port '{}'" .format(index, port_name)) try: if assignments[x][y][0][subblock] is not None or assignments[x][y][1][subblock] is not None: raise PRGAAPIError("Conflicting assignment at ({}, {}, {})" .format(x, y, subblock)) assignments[x][y][port.direction.case(0, 1)][subblock] = name processed[port.direction.case("", "out:") + name] = x, y, subblock except (IndexError, TypeError): raise PRGAAPIError("Cannot assign port '{}' to ({}, {}, {})" .format(name, x, y, subblock)) # assign IOs next_io = {d: None for d in PortDirection} for port_name, port in iteritems(mod_top.ports): key = port.direction.case("", "out:") + port_name if port.low is None: if key in processed: continue io = next_io[port.direction] = _find_next_available_io(assignments, next_io[port.direction], port.direction) if io is None: raise PRGAAPIError("Ran out of IOs when assigning '{}'".format(port_name)) x, y, subblock = io assignments[x][y][port.direction.case(0, 1)][subblock] = key processed[key] = io else: for i in range(port.low, port.high): bit_name = '{}[{}]'.format(key, i) if bit_name in processed: continue io = next_io[port.direction] = _find_next_available_io(assignments, next_io[port.direction], port.direction) if io is None: raise PRGAAPIError("Ran out of IOs when assigning '{}'".format(bit_name)) x, y, subblock = io assignments[x][y][port.direction.case(0, 1)][subblock] = bit_name processed[bit_name] = io return processed
def run(self, context): """Run the flow.""" # 1. resolve dependences/conflicts passes = OrderedDict() while self._passes: updated = None for i, pass_ in enumerate(self._passes): # 1.1 is the exact same pass already run? if pass_.key in passes: raise PRGAAPIError("Pass '{}' is added twice".format( pass_.key)) # 1.2 is there any invalid pass keys? try: duplicate = next( key for key in passes if not self.__key_is_irrelevent(pass_.key, key)) raise PRGAAPIError( "Pass '{}' and '{}' are duplicate, or one is the sub-pass of another" .format(duplicate, pass_.key)) except StopIteration: pass # 1.3 is any pass key in conflict with this key? try: conflict = next(key for key in passes if any( self.__key_is_prefix(rule, key) for rule in pass_.conflicts)) raise PRGAAPIError("Pass '{}' conflicts with '{}'".format( conflict, pass_.key)) except StopIteration: pass # 1.4 are all the dependences satisfied? if all( any(self.__key_is_prefix(rule, key) for key in passes) for rule in pass_.dependences): passes[pass_.key] = pass_ updated = i break if updated is None: missing = { pass_.key: tuple(rule for rule in pass_.dependences if all( not self.__key_is_prefix(rule, key) for key in passes)) for pass_ in self._passes } raise PRGAAPIError("Missing passes:" + "\n\t".join( map( lambda kv: "{} required by {}".format( ', '.join(kv[1]), kv[0]), iteritems(missing)))) else: del self._passes[i] passes = tuple(itervalues(passes)) # 2. order passes # 2.1 build a graph g = nx.DiGraph() g.add_nodes_from(range(len(passes))) for i, pass_ in enumerate(passes): for j, other in enumerate(passes): if i == j: continue if (any( self.__key_is_prefix(rule, other.key) for rule in pass_.passes_before_self) or any( self.__key_is_prefix(rule, other.key) for rule in pass_.dependences)): # ``other`` should be executed before ``pass_`` g.add_edge(j, i) if any( self.__key_is_prefix(rule, other.key) for rule in pass_.passes_after_self): # ``other`` should be executed after ``pass_`` g.add_edge(i, j) try: passes = [passes[i] for i in nx.topological_sort(g)] except nx.exception.NetworkXUnfeasible: raise PRGAAPIError( "Cannot determine a feasible order of the passes") # 3. run passes if self._drop_cache_before_start: context._cache = {} for pass_ in passes: _logger.info("running pass '%s'", pass_.key) t = time.time() pass_.run(context) # context._passes_applied.add(pass_.key) _logger.info("pass '%s' took %f seconds", pass_.key, time.time() - t) if self._drop_cache_after_end: context._cache = {}
help="Name of the generated design-specific synthesis script. 'synth.ys' by default") args = parser.parse_args() enable_stdout_logging(__name__, logging.INFO) # get verilog template env = jj.Environment(loader=jj.FileSystemLoader( os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates'), )) _logger.info("Unpickling architecture context: {}".format(args.context)) context = ArchitectureContext.unpickle(args.context) channel_width = 2 * sum(sgmt.width * sgmt.length for sgmt in itervalues(context.segments)) _logger.info("Reading target design ...") if args.model is None: raise PRGAAPIError("Source file(s) for target design not given") model_top = find_verilog_top(args.model, args.model_top) model_top.parameters = parse_parameters(args.model_parameters) _logger.info("Reading testbench ...") if args.testbench is None: raise PRGAAPIError("Source file(s) for testbench not given") testbench_top = find_verilog_top(args.testbench, args.testbench_top) testbench_top.parameters = parse_parameters(args.testbench_parameters) _logger.info("Assigning IO ...") io_assignments = iobind(context, model_top, parse_io_bindings(args.io) if args.io is not None else {}) ostream = open('io.pads', 'w') for name, (x, y, subblock) in iteritems(io_assignments): ostream.write("{} {} {} {}\n".format(name, x, y, subblock))
def generate_testbench_wrapper(context, template, ostream, tb_top, behav_top, io_bindings): """Generate simulation testbench wrapper. Args: context (`ArchitectureContext`): The architecture context of the custom FPGA template (Jinja2 template): ostream (file-like object): Output file tb_top (`VerilogModule`): Top-level module of the testbench of the behavioral model behav_top (`VerilogModule`): Top-level module of the behavioral model io_bindings (:obj:`Mapping` [:obj:`str` ], :obj:`tuple` [:obj:`int`, :obj:`int`, :obj:`int`]): Mapping from port name in the behavioral model to \(x, y, subblock\) """ # configuration bits config_info = {} total_config_bits = config_info['bs_total_size'] = context.config_circuitry_delegate.total_config_bits last_bit_index = config_info['bs_last_bit_index'] = (total_config_bits - 1) % 64 num_qwords = total_config_bits // 64 if last_bit_index != 63: num_qwords += 1 config_info['bs_num_qwords'] = num_qwords # extract io bindings impl_info = {'name': context.top.name, 'config': []} ports = impl_info['ports'] = {} for name, (x, y, subblock) in iteritems(io_bindings): direction = PortDirection.input_ if name.startswith('out:'): name = name[4:] direction = PortDirection.output matched = _reprog_bit.match(name) port_name = matched.group('name') index = matched.group('index') if index is not None: index = int(index) # 1. is it a valid port in the behavioral model? behav_port = behav_top.ports.get(port_name) if behav_port is None: raise PRGAAPIError("Port '{}' is not found in design '{}'" .format(port_name, behav_top.name)) elif behav_port.direction is not direction: raise PRGAAPIError("Direction mismatch: port '{}' is {} in behavioral model but {} in IO bindings" .format(port_name, behav_port.direction.name, direction)) elif index is None and behav_port.low is not None: raise PRGAAPIError("Port '{}' is a bus and requires an index" .format(port_name)) elif index is not None and (behav_port.low is None or index < behav_port.low or index >= behav_port.high): raise PRGAAPIError("Bit index '{}' is not in port '{}'" .format(index, port_name)) # 2. is (x, y, subblock) an IO block? port = get_external_port(context.top, (x, y), subblock, direction) if port is None: raise PRGAAPIError("No {} IO port found at position ({}, {}, {})" .format(direction, x, y, subblock)) # 3. bind behav_port = copy(behav_port) behav_port.name = name ports[port.name] = behav_port # configuration info _update_config_list(context, context.top, impl_info['config']) # generate testbench wrapper template.stream({ "config": config_info, "behav": behav_top, "tb": tb_top, "impl": impl_info, "iteritems": iteritems, "itervalues": itervalues, }).dump(ostream)