connectPorts function

void connectPorts(
  1. PortReference driver,
  2. PortReference receiver, {
  3. String? driverPathNewPortName,
  4. String? receiverPathNewPortName,
  5. bool allowDriverPathUniquification = true,
  6. bool allowReceiverPathUniquification = true,
  7. SameModuleConnectionType? sameModuleConnectionType,
  8. String? intermediateSignalName,
})

Makes a connection from driver to receiver, punching ports along the way as necessary.

If driverPathNewPortName or receiverPathNewPortName are provided, then it will prefer to name new ports with those names. If allowDriverPathUniquification or allowReceiverPathUniquification are false, then the port names will not be uniquified on those paths. Uniquification also respects BridgeModule.allowUniquification at each level. Repeated connections for the same signal reuse compatible existing hierarchy routes. A different explicitly requested path name creates a distinct alias, while an omitted path name may reuse any existing route.

When driver and receiver are on the same module and the connection is ambiguous (at least one port is PortDirection.inOut and neither is PortDirection.input), sameModuleConnectionType must be provided to disambiguate whether the connection is a SameModuleConnectionType.loopback (using external-facing ports) or a SameModuleConnectionType.passthrough (using internal-facing ports). See PortReference.gets for full details.

If intermediateSignalName is provided, an intermediate signal with that name is inserted on the direct sibling-level segment or same-module connection. For a loopback it appears in the parent module; for a passthrough it appears inside the connected module. When connections with the same intermediateSignalName share a driver, or legally share a bidirectional receiver, the same intermediate signal is reused. The name is ignored for array-typed drivers or vertical (parent/child) connections.

Implementation

void connectPorts(
  PortReference driver,
  PortReference receiver, {
  String? driverPathNewPortName,
  String? receiverPathNewPortName,
  bool allowDriverPathUniquification = true,
  bool allowReceiverPathUniquification = true,
  SameModuleConnectionType? sameModuleConnectionType,
  String? intermediateSignalName,
}) {
  if (driver.module.hasBuilt || receiver.module.hasBuilt) {
    throw RohdBridgeException('Cannot connect ports after build.');
  }

  final explicitDriverPathName = driverPathNewPortName;
  final explicitReceiverPathName = receiverPathNewPortName;

  final driverInstance = driver.module;
  final receiverInstance = receiver.module;

  BridgeModule? commonParent;

  final driverIsReceiver = driver.module == receiver.module;

  final driverContainsReceiver =
      (driver.module.getHierarchyDownTo(receiver.module) != null) &&
          !driverIsReceiver;

  final receiverContainsDriver =
      (receiver.module.getHierarchyDownTo(driver.module) != null) &&
          !driverIsReceiver;

  if (!driverContainsReceiver &&
      !receiverContainsDriver &&
      driverInstance != receiverInstance &&
      (driver.direction == receiver.direction) &&
      ((driver.direction != PortDirection.inOut) ||
          (receiver.direction != PortDirection.inOut))) {
    // e.g. feed-through
    throw RohdBridgeException(
        'Unhandled directionality and hierarchy of driver and receiver.');
  } else if ((driverContainsReceiver || receiverContainsDriver) &&
      (receiver.direction != driver.direction) &&
      (receiver.direction != PortDirection.inOut &&
          driver.direction != PortDirection.inOut)) {
    final containsStr = driverContainsReceiver
        ? 'driver ${driver.module.name} contains'
            ' receiver ${receiver.module.name}'
        : 'receiver ${receiver.module.name} contains'
            ' driver ${driver.module.name}';

    throw RohdBridgeException(
        'Vertical connections should have the same direction,'
        ' but with $driver driving $receiver, '
        ' $containsStr, but directions are'
        ' ${driver.direction} and ${receiver.direction}, respectively.');
  } else {
    commonParent =
        findCommonParent(driverInstance, receiverInstance) as BridgeModule?;

    if (driverContainsReceiver || receiverContainsDriver) {
      if (receiver.portName == driver.portName) {
        // if we're going up/down and the port names are the same, then we
        // should keep the intermediate name the same
        driverPathNewPortName ??= driver.portName;
        receiverPathNewPortName ??= receiver.portName;
      }
    }
  }
  if (commonParent == null) {
    throw RohdBridgeException('No common parent found between'
        ' $driverInstance and $receiverInstance');
  }

  // start from the driver
  var driverPortRef = driver;

  if (driverInstance != commonParent) {
    // we need to punch upwards from the driver to the common parent

    final driverPath = commonParent.getHierarchyDownTo(driverInstance)!;

    for (var i = driverPath.length - 2; i >= 1; i--) {
      final driverPathI = driverPath[i] as BridgeModule;
      final existingPort = driverPathI._findPunchedUpPort(
        driverPortRef,
        explicitDriverPathName,
      );
      if (existingPort != null) {
        driverPortRef = existingPort;
        continue;
      }

      final uniqName = driverPathI._getUniquePortName(
        driverPortRef,
        initialName: driverPathNewPortName,
        allowNameUniquification: allowDriverPathUniquification,
      );

      final lowerPortRef = driverPortRef;
      driverPortRef = lowerPortRef.punchUpTo(
        driverPathI,
        newPortName: uniqName,
      );
      driverPathI._recordPunchedUpPort(
        lowerPortRef,
        driverPortRef,
        requestedName: explicitDriverPathName,
      );
    }
  }

  // now start from the receiver, pulling up
  var receiverPortRef = receiver;

  // keep track of all created receiver ports so far so we can update
  // the corresponding modules' [_upperSourceMap]s
  final createdReceiverPorts = <PortReference>[];

  void recordReceiverPathToDriver() {
    for (final createdReceiverPort in [receiver, ...createdReceiverPorts]) {
      createdReceiverPort.module._recordUpperSourcePort(
        driverPortRef,
        createdReceiverPort,
        requestedName: explicitReceiverPathName,
      );
    }
  }

  if (receiverInstance != commonParent) {
    // we need to punch upwards from the receiver to the common parent

    final receiverPath = commonParent.getHierarchyDownTo(receiverInstance)!;

    for (var i = receiverPath.length - 2; i >= 1; i--) {
      // find if there are ports that are already connected to the driver from
      // anywhere up the chain
      final upperTargets = TraverseableCollection<PortReference>()
        ..add(driverPortRef);
      // TODO(mkorbel1): is there a more efficient way to do this search? can
      //  something be cached efficiently?

      final receiverPathI = receiverPath[i] as BridgeModule;

      for (var upperTargIdx = 0;
          upperTargIdx < upperTargets.length;
          upperTargIdx++) {
        final upperTarg = upperTargets[upperTargIdx];
        final upperTargTargs = receiverPath
            .getRange(1, i + 1)
            .map((receiverPathMod) => (receiverPathMod as BridgeModule)
                ._findUpperSourcePort(upperTarg, explicitReceiverPathName))
            .nonNulls;

        for (final iterUpperTargi in upperTargTargs) {
          final existingPort = receiverPathI._findUpperSourcePort(
            iterUpperTargi,
            explicitReceiverPathName,
          );
          if (existingPort != null) {
            // if we already have a known connection up to the driver from
            // here, then we can just connect to the existing port and exit
            // immediately
            receiverPortRef.gets(existingPort);
            recordReceiverPathToDriver();
            return;
          }

          upperTargets.add(iterUpperTargi);
        }
      }

      final uniqName = receiverPathI._getUniquePortName(
        receiverPortRef,
        initialName: receiverPathNewPortName,
        allowNameUniquification: allowReceiverPathUniquification,
      );

      receiverPortRef =
          receiverPortRef.punchUpTo(receiverPathI, newPortName: uniqName);

      createdReceiverPorts.add(receiverPortRef);

      // now we tell all prior-created ports that they can access the current
      // receiver port via the port that was created.
      for (final createdReceiverPort in createdReceiverPorts) {
        final receiverPortModule = createdReceiverPort.module;
        assert(
            receiverPortModule._findUpperSourcePort(
                    receiverPortRef, explicitReceiverPathName) ==
                null,
            'should not be recreating a path if one already exists.');
        receiverPortModule._recordUpperSourcePort(
          receiverPortRef,
          createdReceiverPort,
          requestedName: explicitReceiverPathName,
        );
      }
    }
  }

  // also notify about the top-level driver
  recordReceiverPathToDriver();

  receiverPortRef.gets(driverPortRef,
      sameModuleConnectionType: sameModuleConnectionType,
      intermediateSignalName: intermediateSignalName);

  if (receiverInstance == commonParent &&
      driverPortRef.module != commonParent &&
      driverPortRef.direction == receiverPortRef.direction &&
      driverPortRef.width == receiverPortRef.width) {
    commonParent._recordPunchedUpPort(
      driverPortRef,
      receiverPortRef,
      requestedName: explicitDriverPathName,
    );
  }
}