connectPorts function
- PortReference driver,
- PortReference receiver, {
- String? driverPathNewPortName,
- String? receiverPathNewPortName,
- bool allowDriverPathUniquification = true,
- bool allowReceiverPathUniquification = true,
- SameModuleConnectionType? sameModuleConnectionType,
- String? intermediateSignalName,
- bool allowIntermediateSignalNameUniquification = true,
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.
The intermediate signal name defaults to the explicitly supplied
receiverPathNewPortName, then driverPathNewPortName, unless overridden
by intermediateSignalName. If all three are omitted, naming is automatic.
When a name is selected, 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 resolved name
share a driver, or legally share a bidirectional receiver with a compatible
shape and naming policy, an existing intermediate is reused. Names may be
uniquified on collision. Only Bridge-created intermediates are tracked for
reuse; manually connected signals are not adopted by name. Whole arrays and
structures preserve type and shape with the requested name. Custom
aggregates use their Logic.clone contract. Regular array selections retain
their dimensions and packed/unpacked layout; bit selections use packed
intermediates. Structure names appear
through their emitted field names. These intermediates do not change the
existing representation of hierarchy ports, including flattened structures.
The name is ignored for vertical (parent/child) connections.
allowIntermediateSignalNameUniquification defaults to true. Setting it
to false reserves the resolved intermediate name, including a name taken
from either path-name fallback. Incompatible reserved-name collisions fail
during synthesis rather than being renamed. This flag is independent of
path-port uniquification and has no effect when no intermediate is named.
Allowing uniquification does not weaken an existing or custom clone's
reserved naming policy.
A stricter naming request can create a separate alias for a new fan-out receiver or a bidirectional net without changing existing intermediates. An already-connected non-net receiver cannot be rewired this way. In strict mode, custom structure clones must reserve each emitted field or array name with the requested structure name and an underscore as a prefix. Bridge preserves the concrete type and does not replace fields to change their naming policy or extend ROHD's synthesis support for that type. Unsupported clones and attempts to rewire non-net receivers throw RohdBridgeException before connecting the intermediate. Hierarchy routes may already have been created by that point.
Implementation
void connectPorts(
PortReference driver,
PortReference receiver, {
String? driverPathNewPortName,
String? receiverPathNewPortName,
bool allowDriverPathUniquification = true,
bool allowReceiverPathUniquification = true,
SameModuleConnectionType? sameModuleConnectionType,
String? intermediateSignalName,
bool allowIntermediateSignalNameUniquification = true,
}) {
if (driver.module.hasBuilt || receiver.module.hasBuilt) {
throw RohdBridgeException('Cannot connect ports after build.');
}
final explicitDriverPathName = driverPathNewPortName;
final explicitReceiverPathName = receiverPathNewPortName;
final resolvedIntermediateSignalName = intermediateSignalName ??
explicitReceiverPathName ??
explicitDriverPathName;
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,
intermediateSignalName: resolvedIntermediateSignalName);
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: resolvedIntermediateSignalName,
allowIntermediateSignalNameUniquification:
allowIntermediateSignalNameUniquification);
if (receiverInstance == commonParent &&
driverPortRef.module != commonParent &&
driverPortRef.direction == receiverPortRef.direction &&
driverPortRef.width == receiverPortRef.width) {
commonParent._recordPunchedUpPort(
driverPortRef,
receiverPortRef,
requestedName: explicitDriverPathName,
);
}
}