From e53536ae8e18d44a18761e029e50d3e35c7a124c Mon Sep 17 00:00:00 2001 From: "Korbel, Max" Date: Sun, 8 Mar 2026 09:55:24 -0700 Subject: [PATCH 1/2] Fixed so that sub interface drive and receive other works --- lib/src/references/interface_reference.dart | 44 ++++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/lib/src/references/interface_reference.dart b/lib/src/references/interface_reference.dart index 8adfa86..741cd4b 100644 --- a/lib/src/references/interface_reference.dart +++ b/lib/src/references/interface_reference.dart @@ -511,22 +511,46 @@ extension _ExceptPairInterfaceExtensions on PairInterface { /// [exceptPorts]. void _driveOtherExcept(PairInterface other, Iterable tags, {required Set? exceptPorts}) { - getPorts(tags).forEach((portName, thisPort) { - if (exceptPorts == null || !exceptPorts.contains(portName)) { - other.port(portName) <= thisPort; - } - }); + final subInterfacesPresent = + subInterfaces.isNotEmpty || other.subInterfaces.isNotEmpty; + if (subInterfacesPresent && + (exceptPorts != null && exceptPorts.isNotEmpty)) { + throw RohdBridgeException( + 'Cannot use exceptPorts when driving interfaces with sub-interfaces'); + } + + if (subInterfacesPresent) { + driveOther(other, tags); + } else { + getPorts(tags).forEach((portName, thisPort) { + if (exceptPorts == null || !exceptPorts.contains(portName)) { + other.port(portName) <= thisPort; + } + }); + } } /// Performs the same operation as [receiveOther], but excludes ports listed /// in [exceptPorts]. void _receiveOtherExcept(PairInterface other, Iterable tags, {required Set? exceptPorts}) { - getPorts(tags).forEach((portName, thisPort) { - if (exceptPorts == null || !exceptPorts.contains(portName)) { - thisPort <= other.port(portName); - } - }); + final subInterfacesPresent = + subInterfaces.isNotEmpty || other.subInterfaces.isNotEmpty; + if (subInterfacesPresent && + (exceptPorts != null && exceptPorts.isNotEmpty)) { + throw RohdBridgeException( + 'Cannot use exceptPorts when driving interfaces with sub-interfaces'); + } + + if (subInterfacesPresent) { + receiveOther(other, tags); + } else { + getPorts(tags).forEach((portName, thisPort) { + if (exceptPorts == null || !exceptPorts.contains(portName)) { + thisPort <= other.port(portName); + } + }); + } } /// Creates a copy of an interface with optional port exclusions. From 3c00096e8b584cb11b0ac3d938db749298026d1f Mon Sep 17 00:00:00 2001 From: "Korbel, Max" Date: Sun, 8 Mar 2026 12:33:20 -0700 Subject: [PATCH 2/2] fix so that sub interfaces work, and throw exceptions with except ports, incl tests --- lib/src/references/interface_reference.dart | 17 +- test/sub_interface_test.dart | 255 ++++++++++++++++++++ 2 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 test/sub_interface_test.dart diff --git a/lib/src/references/interface_reference.dart b/lib/src/references/interface_reference.dart index 741cd4b..4cb4195 100644 --- a/lib/src/references/interface_reference.dart +++ b/lib/src/references/interface_reference.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2024-2025 Intel Corporation +// Copyright (C) 2024-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // interface_reference.dart @@ -509,6 +509,9 @@ class InterfaceReference extension _ExceptPairInterfaceExtensions on PairInterface { /// Performs the same operation as [driveOther], but excludes ports listed in /// [exceptPorts]. + /// + /// Throws [RohdBridgeException] if [exceptPorts] is provided and not empty + /// when either this interface or [other] has sub-interfaces. void _driveOtherExcept(PairInterface other, Iterable tags, {required Set? exceptPorts}) { final subInterfacesPresent = @@ -532,6 +535,9 @@ extension _ExceptPairInterfaceExtensions on PairInterface { /// Performs the same operation as [receiveOther], but excludes ports listed /// in [exceptPorts]. + /// + /// Throws [RohdBridgeException] if [exceptPorts] is provided and not empty + /// when either this interface or [other] has sub-interfaces. void _receiveOtherExcept(PairInterface other, Iterable tags, {required Set? exceptPorts}) { final subInterfacesPresent = @@ -561,7 +567,16 @@ extension _ExceptPairInterfaceExtensions on PairInterface { /// /// This is used internally when creating interface variants that exclude /// certain ports during hierarchical interface operations. + /// + /// Throws [RohdBridgeException] if [exceptPorts] is provided and not empty + /// when this interface has sub-interfaces. PairInterface _cloneExcept({required Set? exceptPorts}) { + if (subInterfaces.isNotEmpty && + (exceptPorts != null && exceptPorts.isNotEmpty)) { + throw RohdBridgeException( + 'Cannot use exceptPorts when cloning interfaces with sub-interfaces'); + } + if (exceptPorts == null || exceptPorts.isEmpty) { return clone(); } diff --git a/test/sub_interface_test.dart b/test/sub_interface_test.dart new file mode 100644 index 0000000..b29beac --- /dev/null +++ b/test/sub_interface_test.dart @@ -0,0 +1,255 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// sub_interface_test.dart +// Tests for interfaces with sub-interfaces. +// +// 2026 March 8 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_bridge/rohd_bridge.dart'; +import 'package:test/test.dart'; + +class SubIntf extends PairInterface { + SubIntf() + : super( + portsFromProvider: [Logic.port('subFp', 8)], + portsFromConsumer: [Logic.port('subFc', 8)], + ); + + @override + SubIntf clone() => SubIntf(); +} + +class IntfWithSub extends PairInterface { + IntfWithSub() + : super( + portsFromProvider: [Logic.port('topFp', 8)], + portsFromConsumer: [Logic.port('topFc', 8)], + ) { + addSubInterface('sub', SubIntf()); + } + + @override + IntfWithSub clone() => IntfWithSub(); +} + +void main() { + group('interfaces with sub-interfaces', () { + test('connectUpTo passes signals through sub-interfaces', () async { + final top = BridgeModule('top') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.consumer); + + final leaf = BridgeModule('leaf') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.consumer); + + top.addSubModule(leaf); + + leaf.interface('intf').connectUpTo(top.interface('intf')); + + top.pullUpPort(leaf.createPort('dummy', PortDirection.inOut)); + + await top.build(); + + // top-level interface ports + top.interface('intf').port('topFp').port.put(0xAB); + expect(leaf.interface('intf').port('topFp').port.value.toInt(), 0xAB); + + leaf.interface('intf').port('topFc').port.put(0xCD); + expect(top.interface('intf').port('topFc').port.value.toInt(), 0xCD); + + // sub-interface ports (accessed via module ports) + top.input('intf_subFp').put(0x12); + expect(leaf.input('intf_subFp').value.toInt(), 0x12); + + leaf.output('intf_subFc').put(0x34); + expect(top.output('intf_subFc').value.toInt(), 0x34); + }); + + test('connectDownTo passes signals through sub-interfaces', () async { + final top = BridgeModule('top') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.provider); + + final leaf = BridgeModule('leaf') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.provider); + + top.addSubModule(leaf); + + top.interface('intf').connectDownTo(leaf.interface('intf')); + + top.pullUpPort(leaf.createPort('dummy', PortDirection.inOut)); + + await top.build(); + + // top-level interface ports + top.interface('intf').port('topFp').port.put(0xAB); + expect(leaf.interface('intf').port('topFp').port.value.toInt(), 0xAB); + + leaf.interface('intf').port('topFc').port.put(0xCD); + expect(top.interface('intf').port('topFc').port.value.toInt(), 0xCD); + + // sub-interface ports (accessed via module ports) + top.input('intf_subFc').put(0x12); + expect(leaf.input('intf_subFc').value.toInt(), 0x12); + + leaf.output('intf_subFp').put(0x34); + expect(top.output('intf_subFp').value.toInt(), 0x34); + }); + + test('connectTo passes signals through sub-interfaces', () async { + final top = BridgeModule('top'); + + final provider = BridgeModule('provider') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.provider); + + final consumer = BridgeModule('consumer') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.consumer); + + top + ..addSubModule(provider) + ..addSubModule(consumer); + + provider.interface('intf').connectTo(consumer.interface('intf')); + + top + ..pullUpPort(provider.createPort('dummy', PortDirection.inOut)) + ..pullUpPort(consumer.createPort('dummy', PortDirection.inOut)); + + await top.build(); + + // top-level interface ports + provider.interface('intf').port('topFp').port.put(0xAB); + expect(consumer.interface('intf').port('topFp').port.value.toInt(), 0xAB); + + consumer.interface('intf').port('topFc').port.put(0xCD); + expect(provider.interface('intf').port('topFc').port.value.toInt(), 0xCD); + + // sub-interface ports (accessed via module ports) + provider.output('intf_subFp').put(0x12); + expect(consumer.input('intf_subFp').value.toInt(), 0x12); + + consumer.output('intf_subFc').put(0x34); + expect(provider.input('intf_subFc').value.toInt(), 0x34); + }); + + test('connectUpTo throws when exceptPorts used with sub-interfaces', () { + final top = BridgeModule('top') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.consumer); + + final leaf = BridgeModule('leaf') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.consumer); + + top.addSubModule(leaf); + + expect( + () => leaf + .interface('intf') + .connectUpTo(top.interface('intf'), exceptPorts: {'topFp'}), + throwsA(isA()), + ); + }); + + test('connectDownTo throws when exceptPorts used with sub-interfaces', () { + final top = BridgeModule('top') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.provider); + + final leaf = BridgeModule('leaf') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.provider); + + top.addSubModule(leaf); + + expect( + () => top + .interface('intf') + .connectDownTo(leaf.interface('intf'), exceptPorts: {'topFp'}), + throwsA(isA()), + ); + }); + + test('connectTo throws when exceptPorts used with sub-interfaces', () { + final top = BridgeModule('top'); + + final provider = BridgeModule('provider') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.provider); + + final consumer = BridgeModule('consumer') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.consumer); + + top + ..addSubModule(provider) + ..addSubModule(consumer); + + expect( + () => provider + .interface('intf') + .connectTo(consumer.interface('intf'), exceptPorts: {'topFp'}), + throwsA(isA()), + ); + }); + + test('punchUpTo throws when exceptPorts used with sub-interfaces', () { + final top = BridgeModule('top'); + + final leaf = BridgeModule('leaf') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.consumer); + + top.addSubModule(leaf); + + expect( + () => leaf.interface('intf').punchUpTo(top, exceptPorts: {'topFp'}), + throwsA(isA()), + ); + }); + + test('punchDownTo throws when exceptPorts used with sub-interfaces', () { + final top = BridgeModule('top') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.provider); + + final leaf = BridgeModule('leaf'); + top.addSubModule(leaf); + + expect( + () => top.interface('intf').punchDownTo(leaf, exceptPorts: {'topFp'}), + throwsA(isA()), + ); + }); + + test('punchUpTo passes signals through sub-interfaces', () async { + final top = BridgeModule('top'); + + final leaf = BridgeModule('leaf') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.consumer); + + top.addSubModule(leaf); + + leaf.interface('intf').punchUpTo(top); + + await top.build(); + + top.input('intf_subFp').put(0x12); + expect(leaf.input('intf_subFp').value.toInt(), 0x12); + + leaf.output('intf_subFc').put(0x34); + expect(top.output('intf_subFc').value.toInt(), 0x34); + }); + + test('punchDownTo passes signals through sub-interfaces', () async { + final top = BridgeModule('top') + ..addInterface(IntfWithSub(), name: 'intf', role: PairRole.provider); + + final leaf = BridgeModule('leaf'); + top.addSubModule(leaf); + + top.interface('intf').punchDownTo(leaf); + + await top.build(); + + top.input('intf_subFc').put(0x12); + expect(leaf.input('intf_subFc').value.toInt(), 0x12); + + leaf.output('intf_subFp').put(0x34); + expect(top.output('intf_subFp').value.toInt(), 0x34); + }); + }); +}