예제 #1
0
 def _elaborate(self):
     """Verify all the ``clock`` and ``combinational_sources`` attributes are valid."""
     for port in itervalues(self.ports):
         if port.is_clock:
             continue
         if port.clock is not None:
             clock = self.ports.get(port.clock, None)
             if clock is None:
                 raise PRGAInternalError(
                     "Clock '{}' of port '{}' not found in primitive '{}'".
                     format(port.clock, port, self))
             elif not clock.is_clock:
                 raise PRGAInternalError(
                     "Clock '{}' of port '{}' in primitive '{}' is not a clock"
                     .format(clock, port, self))
         if port.direction.is_input:
             continue
         for source_name in port.combinational_sources:
             source = self.ports.get(source_name, None)
             if source is None:
                 raise PRGAInternalError(
                     "Combinational source '{}' of port '{}' not found in primitive '{}'"
                     .format(source_name, port, self))
             elif not source.direction.is_input:
                 raise PRGAInternalError(
                     "Combinational source '{}' of port '{}' in primitive '{}' is not an input"
                     .format(source_name, port, self))
예제 #2
0
    def segment_fc(self, segment, all_sections=False):
        """Get the FC value for a specific segment.

        Args:
            segment (`Segment`):
            all_sections (:obj:`bool`): if all sections of a segment longer than 1 should be taken into consideration

        Returns:
            :obj:`int`: the calculated FC value
        """
        multiplier = segment.length if all_sections else 1
        fc = self.overrides.get(segment.name, self.default)
        if isinstance(fc, int):
            if fc < 0 or fc >= segment.width:
                raise PRGAInternalError(
                    "Invalid FC value ({}) for segment '{}'".format(
                        fc, segment.name))
            return fc * multiplier
        elif isinstance(fc, float):
            if fc < 0 or fc > 1:
                raise PRGAInternalError(
                    "Invalid FC value ({}) for segment '{}'".format(
                        fc, segment.name))
            return int(ceil(fc * segment.width * multiplier))
        else:
            raise PRGAInternalError(
                "Invalid FC value ({}) for segment '{}'".format(
                    fc, segment.name))
예제 #3
0
def hierarchical_source(net, inplace=False):
    """Get the direct driver of hierarchical bit, ``net``."""
    instance, bit = net
    if bit.is_bus:
        raise PRGAInternalError("'{}' is a bus".format(net))
    elif bit.net_type.is_pin:
        raise PRGAInternalError("'{}' is a pin".format(net))
    if bit.direction.is_output:
        if bit.parent.is_leaf_module or bit.logical_source is UNCONNECTED:
            # we don't search combinational paths through a leaf module here.
            # do that in the caller's context
            return None
        else:
            return hierarchical_net(bit.logical_source, instance, inplace)
    elif len(instance) == 0:
        return None
    else:
        if inplace:
            leaf = instance.pop()
        else:
            instance, leaf = instance[:-1], instance[-1]
        source_bit = leaf.logical_pins[bit.bus.key][bit.index].logical_source
        if source_bit is UNCONNECTED:
            return None
        else:
            return hierarchical_net(source_bit, instance, inplace)
예제 #4
0
    def _add_port(self, port):
        """Add a port to this module.

        Args:
            port (`AbstractPort`):

        Returns:
            `AbstractPort`: Echoing back the added port
        """
        if port.in_physical_domain and not self.in_physical_domain:
            raise PRGAInternalError(
                "Cannot add a physical port '{}' to a non-physical module '{}'"
                .format(port, self))
        elif port.in_logical_domain and not self.in_logical_domain:
            raise PRGAInternalError(
                "Cannot add a logical port '{}' to a non-logical module '{}'".
                format(port, self))
        elif port.parent is not self:
            raise PRGAInternalError(
                "Module '{}' is not the parent module of port '{}'".format(
                    self, port))
        elif port.key in self._ports:
            raise PRGAInternalError(
                "Key '{}' for port '{}' already exists in module '{}'".format(
                    port.key, port, self))
        try:
            return self._ports.setdefault(port.key, port)
        except AttributeError:
            raise PRGAInternalError(
                "Cannot add port '{}' to module '{}'. Port mapping is read-only"
                .format(port, self))
예제 #5
0
    def _add_instance(self, instance):
        """Add an instance to this module.

        Args:
            instance (`AbstractInstance`):

        Returns:
            `AbstractInstance`: Echoing back the added instance
        """
        if instance.in_physical_domain and not self.in_physical_domain:
            raise PRGAInternalError(
                "Cannot add a physical instance '{}' to a non-physical module '{}'"
                .format(instance, self))
        elif instance.in_logical_domain and not self.in_logical_domain:
            raise PRGAInternalError(
                "Cannot add a logical instance '{}' to a non-logical module '{}'"
                .format(instance, self))
        elif instance.parent is not self:
            raise PRGAInternalError(
                "Module '{}' is not the parent module of instance '{}'".format(
                    self, instance))
        elif instance.key in self._instances:
            raise PRGAInternalError(
                "Key '{}' for instance '{}' already exists in module '{}'".
                format(instance.key, instance, self))
        try:
            return self._instances.setdefault(instance.key, instance)
        except AttributeError:
            raise PRGAInternalError(
                "Cannot add instance '{}' to module '{}'. Instance mapping is read-only"
                .format(instance, self))
예제 #6
0
 def physical_cp(self, cp):
     # 0. shortcut if no changes are to be made
     if cp is self.physical_cp:
         return
     # 1. validate self and parameter
     if self.in_physical_domain:
         raise PRGAInternalError(
             "'{}' is in the physical domain so no counterpart should be set"
             .format(self))
     if cp is not None and not (cp.in_physical_domain
                                and cp.is_sink is self.is_sink):
         raise PRGAInternalError(
             "'{}' is not a valid physical counterpart for '{}'".format(
                 cp, self))
     # 2. ungroup grouped physical counterpart if there are
     try:
         grouped = self.bus._physical_cp
         for i in range(self.bus.width):
             l = self.bus._get_or_create_bit(i, False)
             p = grouped._get_or_create_bit(i, False)
             l._physical_cp = p
         del self.bus._physical_cp
     except AttributeError:
         pass
     # 3. update
     if cp is None:
         try:
             del self._physical_cp
         except AttributeError:
             pass
     else:
         self._physical_cp = cp._get_or_create_static_cp()
예제 #7
0
 def physical_source(self, source):
     # 0. shortcut if no changes are to be made
     try:
         if source is self._physical_source:
             return
     except AttributeError:
         pass
     # 1. physical source is only valid if this is a physical net
     if not self.in_physical_domain:
         raise PRGAInternalError("'{}' is not in the physical domain"
                 .format(self))
     # 2. if ``source`` is a bus or a bit:
     try:
         if source.is_bus:
             # 2.1 validate
             if not (source.in_physical_domain and not source.is_sink and source.width == self.width):
                 raise PRGAInternalError("'{}' is not a {}-bit physical source bus"
                         .format(source, self.width))
             # 2.2 clean up ungrouped physical sources
             try:
                 for bit in self._bits:
                     try:
                         del bit._physical_source
                     except AttributeError:
                         pass
             except AttributeError:
                 pass
             # 2.3 update
             self._physical_source = source
         elif self.width == 1:
             # 2.4 validate
             if not (source.in_physical_domain and not source.is_sink):
                 raise PRGAInternalError("'{}' is not a physical source"
                         .format(source))
             # 2.5 clean up grouped physical source
             try:
                 del self._physical_source
             except AttributeError:
                 pass
             # 2.6 update
             self._get_or_create_bit(0, False)._physical_source = source
     # 3. if ``source`` is a sequence of bits
     except AttributeError:
         # 3.1 validate
         if not len(source) == self.width:
             raise PRGAInternalError("Got {} bits but {} expected"
                     .format(len(source), self.width))
         # 3.2 clean up grouped physical source
         try:
             del self._physical_source
         except AttributeError:
             pass
         # 3.3 create links
         for i, src in enumerate(source):
             # 3.3.1 validate
             if not (src.in_physical_domain and not src.is_sink):
                 raise PRGAInternalError("'{}' is not a physical source"
                         .format(src))
             # 3.3.2 update
             self._get_or_create_bit(i, False)._physical_source = src
예제 #8
0
 def add_user_sources(self, sources):
     if not self.in_user_domain:
         raise PRGAInternalError("'{}' is not a user sink".format(self))
     for s in sources:
         s = s._get_or_create_static_cp()
         if s.is_sink:
             # we allow non-user sources to be added to this list
             # this is only used for connection box - switch box bridges
             # in other cases, checks are done by caller of this function
             raise PRGAInternalError("'{}' is not a source".format(s))
         try:
             if s not in self._user_sources:
                 self._user_sources.append(s)
         except AttributeError:
             self._user_sources = [s]
예제 #9
0
 def _validate_model(self, model):
     """Validate if ``model`` can be instantiate in this module."""
     if model.module_class not in (ModuleClass.primitive,
                                   ModuleClass.cluster):
         raise PRGAInternalError(
             "Only primitives or clusters may be instantiated in a custer/block."
         )
예제 #10
0
    def register_blackbox_template(self,
                                   name,
                                   lib_template=None,
                                   techmap_template=None,
                                   parameters=None,
                                   premap_commands=None):
        """Register a blackbox template.

        Args:
            name (:obj:`str`): Name of the module which activates this entry
            lib_template (:obj:`str`): Template for generating a blackbox library file
            techmap_template (:obj:`str`): Template for generating a techmap file
            parameters (:obj:`Mapping` [:obj:`str`, Any ]): Additional parameters for the template
            premap_commands (:obj:`Sequence` [:obj:`str` ]): Pre-techmap commands in the Yosys script

        Blackbox library is auto-generated for custom primitives, so register blackbox template only if you wish to
        override the blackbox generated by default, or you need to provide techmap file for them.
        """
        if name in self._blackbox_entries:
            raise PRGAInternalError(
                "Blackbox template entry '{}' already registered".format(name))
        return self._blackbox_entries.setdefault(
            name,
            YosysTemplateEntry(lib_template, techmap_template, parameters,
                               premap_commands))
예제 #11
0
    def register_memory_template(self,
                                 name,
                                 lib_template=None,
                                 techmap_template=None,
                                 rule_template=None,
                                 parameters=None,
                                 premap_commands=None):
        """Register a BRAM rule template.

        Args:
            name (:obj:`str`): Name of the module which activates this entry
            lib_template (:obj:`str`): Template for generating a blackbox library file
            techmap_template (:obj:`str`): Template for generating a techmap file
            rule_template (:obj:`str`): Template for generating a BRAM rule
            parameters (:obj:`Mapping` [:obj:`str`, Any ]): Additional parameters for the template
            premap_commands (:obj:`Sequence` [:obj:`str` ]): Pre-techmap commands in the Yosys script
        """
        if name in self._memory_entries:
            raise PRGAInternalError(
                "Memory rule template entry '{}' already registered".format(
                    name))
        return self._memory_entries.setdefault(
            name,
            YosysMemoryTemplateEntry(lib_template, techmap_template,
                                     rule_template, parameters,
                                     premap_commands))
예제 #12
0
 def user_sources(self):
     if not self.in_user_domain:
         raise PRGAInternalError("'{}' is not a user sink".format(self))
     try:
         return ReadonlySequenceProxy(self._user_sources)
     except AttributeError:
         return tuple()
예제 #13
0
 def _connect(self, source, sink):
     if not (source.parent is self and not source.is_sink
             and source.net_class.is_node and
             (source.in_user_domain or
              (source.bus.node.node_type.is_segment_bridge and
               (source.bus.node.bridge_type.is_sboxin_cboxout
                or source.bus.node.bridge_type.is_sboxin_cboxout2)))):
         raise PRGAInternalError(
             "'{}' is not a user-accessible sink node in routing box '{}'".
             format(source, self))
     if not (sink.parent is self and sink.is_sink and sink.net_class.is_node
             and sink.in_user_domain):
         raise PRGAInternalError(
             "'{}' is not a user-accessible source node in routing box '{}'"
             .format(sink, self))
     sink.add_user_sources((source, ))
예제 #14
0
 def remove_user_sources(self, sources=None):
     # 0. user sources are only valid if this is a user-accessible net
     if not self.in_user_domain:
         raise PRGAInternalError(
             "'{}' is not a user-accessible sink".format(self))
     # 1. static bits are always created when user sources are involved
     return self._get_or_create_static_cp().remove_user_sources(sources)
예제 #15
0
 def remove_user_sources(self, sources=None):
     if not self.in_user_domain:
         raise PRGAInternalError("'{}' is not a user sink".format(self))
     if sources is None:
         try:
             del self._user_sources
         except AttributeError:
             pass
     else:
         for s in sources:
             s = s._get_or_create_static_cp()
             try:
                 self._user_sources.remove(s)
             except ValueError:
                 raise PRGAInternalError(
                     "'{}' is not a user source of user sink '{}'".format(
                         s, self))
예제 #16
0
 def __init__(self, name, width=1, is_clock=False):
     if is_clock and width != 1:
         raise PRGAInternalError("Clock wire must be 1-bit wide")
     self._name = name
     self._width = width
     self._is_clock = is_clock
     self._bound_to_position = None
     self._bound_to_subblock = None
예제 #17
0
def get_switch_path(context, source, sink, drop_cache = False):
    """Get the switch path from ``source`` to ``sink``.

    Args:
        context (`ArchitectureContext`):
        source (`AbstractSourceBit`):
        sink (`AbstractSinkBit`):
        drop_cache (:obj:`bool`):

    Returns:
        :obj:`Sequence` [`AbstractSinkBit` ]: A sequence of switch input bits
    """
    if sink.logical_source is source:
        return tuple()
    module = source.parent if source.net_type.is_port else source.parent.parent
    if not drop_cache:
        cache = context._cache.get('util.switch_path', {}).get(module.name, {}).get(
                _in_cache_bit_id(sink), None)
        if cache is not None:
            path = cache.get(_in_cache_bit_id(source))
            if path is None:
                raise PRGAInternalError("No path from '{}' to '{}' in module '{}'"
                        .format(source, sink, module))
            else:
                return path
    # map all sources of sink
    sink_id = _in_cache_bit_id(sink)
    stack = [(sink, tuple())]
    cache = context._cache.setdefault('util.switch_path', {}).setdefault(module.name, {}).setdefault(
            _in_cache_bit_id(sink), {})
    while stack:
        cur_sink, cur_path = stack.pop()
        cur_source = cur_sink.logical_source
        if cur_source.net_type.is_const:    # UNCONNECTED or CONSTANT connection
            continue
        if cur_source.net_type.is_pin and cur_source.net_class.is_switch:       # switch output
            for next_sink in cur_source.parent.switch_inputs:
                stack.append( (next_sink, cur_path + (next_sink, )) )
        else:                                                                   # other stuff
            cache[_in_cache_bit_id(cur_source)] = cur_path
    try:
        return cache[_in_cache_bit_id(source)]
    except KeyError:
        raise PRGAInternalError("No path from '{}' to '{}' in module '{}'"
                .format(source, sink, module))
예제 #18
0
 def _validate_node(self, node, direction=None):
     if node.node_type.is_blockport_bridge:
         if direction is None:
             raise PRGAInternalError(
                 "Unable to determine port direction for node '{}' in array element '{}'"
                 .format(node, self))
         return direction
     elif node.node_type.is_segment_bridge:
         if node.bridge_type in (SegmentBridgeType.array_regular,
                                 SegmentBridgeType.array_cboxout,
                                 SegmentBridgeType.array_cboxout2):
             if direction is None:
                 raise PRGAInternalError(
                     "Unable to determine port direction for node '{}' in array element '{}'"
                     .format(node, self))
             return direction
     raise PRGAInternalError(
         "Invalid node '{}' in array element '{}'".format(node, self))
예제 #19
0
 def _validate_node(self, node, direction=None):
     if node.node_type.is_segment_driver:
         if direction is PortDirection.input_:
             raise PRGAInternalError(
                 "Invalid port direction '{}' for node '{}' in switch box '{}'"
                 .format(direction.name, node, self))
         return PortDirection.output
     elif node.node_type.is_segment_bridge:
         if node.bridge_type in (SegmentBridgeType.sboxin_regular,
                                 SegmentBridgeType.sboxin_cboxout,
                                 SegmentBridgeType.sboxin_cboxout2):
             if direction is PortDirection.output:
                 raise PRGAInternalError(
                     "Invalid port direction '{}' for node '{}' in switch box '{}'"
                     .format(direction.name, node, self))
             return PortDirection.input_
     raise PRGAInternalError("Invalid node '{}' in switch box '{}'".format(
         node, self))
예제 #20
0
 def perpendicular(self):
     """`Dimension`: The perpendicular dimension of this dimension."""
     if self is Dimension.x:
         return Dimension.y
     elif self is Dimension.y:
         return Dimension.x
     else:
         raise PRGAInternalError(
             "{} does not have a perpendicular Dimension".format(self))
예제 #21
0
 def direction(self):
     """:obj:`Direction`: The direction of this orientation."""
     if self in (Orientation.north, Orientation.east):
         return Direction.inc
     elif self in (Orientation.south, Orientation.west):
         return Direction.dec
     else:
         raise PRGAInternalError(
             "{} does not have a corresponding direction".format(self))
예제 #22
0
 def _connect(self, source, sink, pack_pattern=None):
     if ((source.net_type.is_port and source.parent is not self) or
         (source.net_type.is_pin and source.parent.parent is not self)
             or source.is_sink or not source.in_user_domain):
         raise PRGAInternalError(
             "'{}' is not a source in the user domain in module '{}'".
             format(source, self))
     if ((sink.net_type.is_port and sink.parent is not self)
             or (sink.net_type.is_pin and sink.parent.parent is not self)
             or not sink.is_sink or not sink.in_user_domain):
         raise PRGAInternalError(
             "'{}' is not a sink in the user domain in module '{}'".format(
                 sink, self))
     sink.add_user_sources((source, ))
     if pack_pattern is not None:
         self._pack_patterns.setdefault(
             (self.__net_id(source), self.__net_id(sink)),
             set()).add(pack_pattern)
예제 #23
0
 def opposite(self):
     """`Direction`: The opposite direction."""
     if self is Direction.inc:
         return Direction.dec
     elif self is Direction.dec:
         return Direction.inc
     else:
         raise PRGAInternalError(
             "{} does not have an opposite Direction".format(self))
예제 #24
0
    def instantiate_element(self, element, position):
        """Instantiate tile or array ``element`` and anchor its \(0, 0\) position to ``position`` in this array.

        Args:
            element (`AbstractArrayElement`):
            position (:obj:`tuple` [:obj:`int`, :obj:`int` ]):
        """
        position = Position(*position)
        # 1. check if the placement fits in the array or conflicts with any existing placements
        for x, y in product(range(-1, element.width), range(-1, element.height)):
            pos_in_elem = Position(x, y)
            pos_in_array = position + pos_in_elem
            for dim in iter(Dimension):
                if element.covers_channel(pos_in_elem, dim):
                    if (not self.covers_channel(pos_in_array, dim) or
                            (element.runs_channel(pos_in_elem, dim) and not self.runs_channel(pos_in_array, dim))):
                        raise PRGAInternalError("Array element '{}' does not fit in array '{}' at {} (channel {})"
                                .format(element, self, pos_in_array, dim.name))
            if element.covers_tile(pos_in_elem):
                if not self.covers_tile(pos_in_array):
                    raise PRGAInternalError("Array element '{}' does not fit in array '{}' at {} (tile)"
                            .format(element, self, position))
                elif self.get_root_element(pos_in_array) is not None:
                    raise PRGAInternalError("Conflicting tile at {} when instantiating array element '{}' at {}"
                            .format(pos_in_array, element, position))
            if element.covers_sbox(pos_in_elem):
                if not self.covers_sbox(pos_in_array):
                    raise PRGAInternalError("Array element '{}' does not fit in array '{}' at {} (switch box)"
                            .format(element, self, position))
                elif (self.sbox_instances.get(pos_in_array, None) is not None or
                        self.get_root_element_for_sbox(pos_in_array) is not None):
                    raise PRGAInternalError("Conflicting switch box at {} when instantiating array element '{}' at {}"
                            .format(pos_in_array, element, position))
        # 2. instantiate and add placeholders
        instance = ArrayElementInstance(self, element, position)
        self._element_grid[position.x][position.y] = instance
        for x, y in product(range(-1, element.width), range(-1, element.height)):
            pos_in_elem = Position(x, y)
            pos_in_array = position + pos_in_elem
            if element.covers_tile(pos_in_elem) and (x != 0 or y != 0):
                self._element_grid[pos_in_array.x][pos_in_array.y] = pos_in_elem
            if element.covers_sbox(pos_in_elem):
                self._sbox_grid[pos_in_array.x][pos_in_array.y] = pos_in_elem
        return instance
예제 #25
0
 def to_bridge_id(self, position=None, section=None, bridge_type=None):
     """`SegmentBridgeID`: Convert to another segment bridge ID."""
     if self.node_type.is_segment_driver and bridge_type is None:
         raise PRGAInternalError(
             "'bridge_type' required when converting segment driver to segment bridge ID"
         )
     return SegmentBridgeID(
         uno(position, self.position), self.prototype, self.orientation,
         uno(section, self.section),
         uno(bridge_type, getattr(self, "bridge_type", None)))
예제 #26
0
 def case(self, *args, **kwargs):
     """Use this enum as a variable in a switch-case clause."""
     try:
         return args[self.value]
     except IndexError:
         try:
             return kwargs[self.name]
         except KeyError:
             raise PRGAInternalError(
                 "Value unspecified for case {}".format(self))
예제 #27
0
 def auto_connect(self, instance, skip_pins=None, quiet=False):
     """Auto connect all pins of ``instance`` to ports with the same key of this module.
     
     Args:
         instance (`AbstractInstance`):
         skip_pins (:obj:`Container`): A set of pin keys which should be skipped
         quiet (:obj:`bool`): If set, skip if no matching port is found for a pin. Otherwise, error is raised
     """
     skip_pins = uno(skip_pins, set())
     if instance.parent is not self:
         raise PRGAInternalError(
             "Module '{}' is not the parent module of instance '{}'".format(
                 self, instance))
     for key, pin in iteritems(instance.pins):
         if key in skip_pins:
             continue
         port = self.ports.get(key)
         if port is None:
             if not quiet:
                 raise PRGAInternalError(
                     "No matching port is found in module '{}' for pin '{}'"
                     .format(self, pin))
             continue
         elif port.direction is not pin.direction:
             if not quiet:
                 raise PRGAInternalError((
                     "Match found for pin '{}' in module '{}', "
                     "but the matched port '{}' is an {} (the pin is an {})"
                 ).format(pin, self, port,
                          port.direction.case('input', 'output'),
                          pin.direction.case('input', 'output')))
             continue
         elif port.width != pin.width:
             if not quiet:
                 raise PRGAInternalError((
                     "Match found for pin '{}' in module '{}', "
                     "but the matched port '{}' is {}-bit wide (the pin is {}-bit wide)"
                 ).format(pin, self, port, port.width, pin.width))
             continue
         elif port.direction.is_input:
             self.connect(port, pin)
         else:
             self.connect(pin, port)
예제 #28
0
    def _add_mode(self, mode):
        """Add a mode to this primitive.

        Args:
            mode (`Mode`):
        """
        if mode.name in self._modes:
            raise PRGAInternalError(
                "Mode '{}' already exist in multi-mode primitive '{}'".format(
                    mode.name, self))
        return self._modes.setdefault(mode.name, mode)
예제 #29
0
 def instantiate_sbox(self, box, position):
     """Instantiate switch box ``box``at ``position`` in this array.
     
     Args:
         box (`SwitchBox`):
         position (:obj:`tuple` [:obj:`int`, :obj:`int` ]):
     """
     position = Position(*position)
     # 1. check if the placement fits in the array or conflicts with any existing placements
     if not self.covers_sbox(position):
         raise PRGAInternalError("Switch box '{}' does not fit in array '{}' at {}"
                 .format(box, self, position))
     elif position in self.sbox_instances:
         raise PRGAInternalError("Conflicting switch box at {} when instantiating switch box '{}' in array {}"
                 .format(position, box, self))
     elif self.get_root_element_for_sbox(position) is not None:
         raise PRGAInternalError("Conflicting tile at {} when instantiating switch box '{}' in array {}"
                 .format(position, box, self))
     # 2. instantiate
     instance = SwitchBoxInstance(self, box, position)
     self._sbox_grid[position.x][position.y] = instance
     return instance
예제 #30
0
 def _validate_node(self, node, direction=None):
     if node.node_type.is_blockport_bridge:
         if direction is node.prototype.direction:
             raise PRGAInternalError(
                 "Invalid port direction '{}' for node '{}' in connection box '{}'"
                 .format(direction.name, node, self))
         return node.prototype.direction.opposite
     elif node.node_type.is_segment_bridge:
         if node.bridge_type.is_cboxin:
             if direction is PortDirection.output:
                 raise PRGAInternalError(
                     "Invalid port direction '{}' for node '{}' in connection box '{}'"
                     .format(direction.name, node, self))
             return PortDirection.input_
         elif node.bridge_type.is_cboxout:
             if direction is PortDirection.input_:
                 raise PRGAInternalError(
                     "Invalid port direction '{}' for node '{}' in connection box '{}'"
                     .format(direction.name, node, self))
             return PortDirection.output
     raise PRGAInternalError(
         "Invalid node '{}' in connection box '{}'".format(node, self))